mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-07-07 20:06:36 +02:00
TDB2 Merge
- Migrated TDB::merge to TDB2::merge. - Removed obsoleted code from TDB. - Modified unit tests for merge to use export instead of export.csv.
This commit is contained in:
parent
45666ebfc5
commit
63eb22fc7e
7 changed files with 740 additions and 1118 deletions
954
src/TDB.cpp
954
src/TDB.cpp
|
@ -44,74 +44,8 @@
|
||||||
#include <Color.h>
|
#include <Color.h>
|
||||||
#include <main.h>
|
#include <main.h>
|
||||||
|
|
||||||
#define NDEBUG
|
|
||||||
#include <assert.h>
|
|
||||||
#include <Taskmod.h>
|
|
||||||
|
|
||||||
#define DEBUG_OUTPUT 0
|
|
||||||
|
|
||||||
#if DEBUG_OUTPUT > 0
|
|
||||||
#define DEBUG_STR(str) std::cout << "DEBUG: " << str << "\n"; std::cout.flush()
|
|
||||||
#define DEBUG_STR_PART(str) std::cout << "DEBUG: " << str; std::cout.flush()
|
|
||||||
#define DEBUG_STR_END(str) std::cout << str << "\n"; std::cout.flush()
|
|
||||||
#else
|
|
||||||
#define DEBUG_STR(str)
|
|
||||||
#define DEBUG_STR_PART(str)
|
|
||||||
#define DEBUG_STR_END(str)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern Context context;
|
extern Context context;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Helper function for TDB::merge
|
|
||||||
void readTaskmods (std::vector <std::string> &input,
|
|
||||||
std::vector <std::string>::iterator &start,
|
|
||||||
std::list<Taskmod> &list)
|
|
||||||
{
|
|
||||||
std::string line;
|
|
||||||
Taskmod tmod_tmp;
|
|
||||||
|
|
||||||
DEBUG_STR ("reading taskmods from file: ");
|
|
||||||
|
|
||||||
for ( ; start != input.end (); ++start)
|
|
||||||
{
|
|
||||||
line = *start;
|
|
||||||
|
|
||||||
if (line.substr (0, 4) == "time")
|
|
||||||
{
|
|
||||||
std::stringstream stream (line.substr (5));
|
|
||||||
long ts;
|
|
||||||
stream >> ts;
|
|
||||||
|
|
||||||
if (stream.fail ())
|
|
||||||
throw std::string ("There was a problem reading the timestamp from the undo.data file.");
|
|
||||||
|
|
||||||
// 'time' is the first line of a modification
|
|
||||||
// thus we will (re)set the taskmod object
|
|
||||||
tmod_tmp.reset (ts);
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (line.substr (0, 3) == "old")
|
|
||||||
{
|
|
||||||
tmod_tmp.setBefore (Task (line.substr (4)));
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (line.substr (0, 3) == "new")
|
|
||||||
{
|
|
||||||
tmod_tmp.setAfter (Task (line.substr (4)));
|
|
||||||
|
|
||||||
// 'new' is the last line of a modification,
|
|
||||||
// thus we can push to the list
|
|
||||||
list.push_back (tmod_tmp);
|
|
||||||
|
|
||||||
assert (tmod_tmp.isValid ());
|
|
||||||
DEBUG_STR (" taskmod complete");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DEBUG_STR ("DONE");
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// The ctor/dtor do nothing.
|
// The ctor/dtor do nothing.
|
||||||
// The lock/unlock methods hold the file open.
|
// The lock/unlock methods hold the file open.
|
||||||
|
@ -242,20 +176,6 @@ int TDB::load (std::vector <Task>& tasks)
|
||||||
// and no other 'status' filters, then loadCompleted can be skipped.
|
// and no other 'status' filters, then loadCompleted can be skipped.
|
||||||
int numberStatusClauses = 0;
|
int numberStatusClauses = 0;
|
||||||
int numberSimpleStatusClauses = 0;
|
int numberSimpleStatusClauses = 0;
|
||||||
/*
|
|
||||||
foreach (att, filter)
|
|
||||||
{
|
|
||||||
if (att->name () == "status")
|
|
||||||
{
|
|
||||||
++numberStatusClauses;
|
|
||||||
|
|
||||||
if (att->mod () == "" &&
|
|
||||||
(att->value () == "pending" ||
|
|
||||||
att->value () == "waiting"))
|
|
||||||
++numberSimpleStatusClauses;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
loadPending (tasks);
|
loadPending (tasks);
|
||||||
|
|
||||||
|
@ -675,885 +595,11 @@ int TDB::nextId ()
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
void TDB::undo ()
|
void TDB::undo ()
|
||||||
{
|
{
|
||||||
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";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
void TDB::merge (const std::string& mergeFile)
|
void TDB::merge (const std::string& mergeFile)
|
||||||
{
|
{
|
||||||
///////////////////////////////////////
|
|
||||||
// Copyright 2010 - 2011, Johannes Schlatow.
|
|
||||||
///////////////////////////////////////
|
|
||||||
|
|
||||||
// list of modifications that we want to add to the local database
|
|
||||||
std::list<Taskmod> mods;
|
|
||||||
|
|
||||||
// list of modifications that we want to add to the local history
|
|
||||||
std::list<Taskmod> mods_history;
|
|
||||||
|
|
||||||
// list of modifications on the local database
|
|
||||||
// has to be merged with mods to create the new undo.data
|
|
||||||
std::list<Taskmod> lmods;
|
|
||||||
|
|
||||||
// will contain the NEW undo.data
|
|
||||||
std::vector <std::string> undo;
|
|
||||||
|
|
||||||
///////////////////////////////////////
|
|
||||||
// initialize the files:
|
|
||||||
|
|
||||||
// load merge file (undo file of right/remote branch)
|
|
||||||
std::vector <std::string> r;
|
|
||||||
if (! File::read (mergeFile, r))
|
|
||||||
throw std::string ("Could not read '") + mergeFile + "'.";
|
|
||||||
|
|
||||||
// file has to contain at least one entry
|
|
||||||
if (r.size () < 3)
|
|
||||||
throw std::string ("There are no changes to merge.");
|
|
||||||
|
|
||||||
// load undo file (left/local branch)
|
|
||||||
Directory location (context.config.get ("data.location"));
|
|
||||||
std::string undoFile = location._data + "/undo.data";
|
|
||||||
|
|
||||||
std::vector <std::string> l;
|
|
||||||
if (! File::read (undoFile, l))
|
|
||||||
throw std::string ("Could not read '") + undoFile + "'.";
|
|
||||||
|
|
||||||
std::string rline, lline;
|
|
||||||
std::vector <std::string>::iterator rit, lit;
|
|
||||||
|
|
||||||
// read first line
|
|
||||||
rit = r.begin ();
|
|
||||||
lit = l.begin ();
|
|
||||||
|
|
||||||
if (rit != r.end())
|
|
||||||
rline = *rit;
|
|
||||||
if (lit != l.end())
|
|
||||||
lline = *lit;
|
|
||||||
|
|
||||||
///////////////////////////////////////
|
|
||||||
// find the branch-off point:
|
|
||||||
|
|
||||||
// first lines are not equal => assuming mergeFile starts at a
|
|
||||||
// later point in time
|
|
||||||
if (lline.compare (rline) != 0)
|
|
||||||
{
|
|
||||||
// iterate in local file to find rline
|
|
||||||
for ( ; lit != l.end (); ++lit)
|
|
||||||
{
|
|
||||||
lline = *lit;
|
|
||||||
|
|
||||||
// push the line to the new undo.data
|
|
||||||
undo.push_back (lline + "\n");
|
|
||||||
|
|
||||||
// found first matching lines?
|
|
||||||
if (lline.compare (rline) == 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add some color.
|
|
||||||
Color colorAdded (context.config.get ("color.sync.added"));
|
|
||||||
Color colorChanged (context.config.get ("color.sync.changed"));
|
|
||||||
Color colorRejected (context.config.get ("color.sync.rejected"));
|
|
||||||
|
|
||||||
// at this point we can assume: (lline==rline) || (lit == l.end())
|
|
||||||
// thus we search for the first non-equal lines or the EOF
|
|
||||||
bool found = false;
|
|
||||||
for ( ; (lit != l.end ()) && (rit != r.end ()); ++lit, ++rit)
|
|
||||||
{
|
|
||||||
lline = *lit;
|
|
||||||
rline = *rit;
|
|
||||||
|
|
||||||
// found first non-matching lines?
|
|
||||||
if (lline.compare (rline) != 0)
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// push the line to the new undo.data
|
|
||||||
undo.push_back (lline + "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "\n";
|
|
||||||
|
|
||||||
///////////////////////////////////////
|
|
||||||
// branch-off point found:
|
|
||||||
if (found)
|
|
||||||
{
|
|
||||||
DEBUG_STR_PART ("Branch-off point found at: ");
|
|
||||||
DEBUG_STR_END (lline);
|
|
||||||
|
|
||||||
std::list<Taskmod> rmods;
|
|
||||||
|
|
||||||
// helper lists
|
|
||||||
std::set<std::string> uuid_new, uuid_left;
|
|
||||||
|
|
||||||
// 1. read taskmods out of the remaining lines
|
|
||||||
readTaskmods (l, lit, lmods);
|
|
||||||
readTaskmods (r, rit, rmods);
|
|
||||||
|
|
||||||
// 2. move new uuids into mods
|
|
||||||
DEBUG_STR_PART ("adding new uuids (left) to skip list...");
|
|
||||||
|
|
||||||
// modifications on the left side are already in the database
|
|
||||||
// we just need them to merge conflicts, so we add new the mods for
|
|
||||||
// new uuids to the skip-list 'uuid_left'
|
|
||||||
std::list<Taskmod>::iterator lmod_it;
|
|
||||||
for (lmod_it = lmods.begin (); lmod_it != lmods.end (); lmod_it++)
|
|
||||||
{
|
|
||||||
if (lmod_it->isNew ())
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
std::cout << "New local task "
|
|
||||||
<< (context.color () ? colorAdded.colorize (lmod_it->getUuid ()) : lmod_it->getUuid ())
|
|
||||||
<< "\n";
|
|
||||||
*/
|
|
||||||
|
|
||||||
uuid_left.insert (lmod_it->getUuid ());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DEBUG_STR_END ("done");
|
|
||||||
DEBUG_STR_PART ("move new uuids (right) to redo list...");
|
|
||||||
|
|
||||||
// new items on the right side need to be inserted into the
|
|
||||||
// local database
|
|
||||||
std::list<Taskmod>::iterator rmod_it;
|
|
||||||
for (rmod_it = rmods.begin (); rmod_it != rmods.end (); )
|
|
||||||
{
|
|
||||||
// we have to save and increment the iterator because we may want to delete
|
|
||||||
// the object from the list
|
|
||||||
std::list<Taskmod>::iterator current = rmod_it++;
|
|
||||||
Taskmod tmod = *current;
|
|
||||||
|
|
||||||
// new uuid?
|
|
||||||
if (tmod.isNew ())
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
std::cout << "Adding new remote task "
|
|
||||||
<< (context.color () ? colorAdded.colorize (tmod.getUuid ()) : tmod.getUuid ())
|
|
||||||
<< "\n";
|
|
||||||
*/
|
|
||||||
|
|
||||||
uuid_new.insert (tmod.getUuid ());
|
|
||||||
mods.push_back (tmod);
|
|
||||||
rmods.erase (current);
|
|
||||||
}
|
|
||||||
else if (uuid_new.find (tmod.getUuid ()) != uuid_new.end ())
|
|
||||||
{
|
|
||||||
// uuid of modification was new
|
|
||||||
mods.push_back (tmod);
|
|
||||||
rmods.erase (current);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DEBUG_STR_END ("done");
|
|
||||||
|
|
||||||
///////////////////////////////////////
|
|
||||||
// merge modifications:
|
|
||||||
DEBUG_STR ("Merging modifications:");
|
|
||||||
|
|
||||||
// we iterate backwards to resolve conflicts by timestamps (newest one wins)
|
|
||||||
std::list<Taskmod>::reverse_iterator lmod_rit;
|
|
||||||
std::list<Taskmod>::reverse_iterator rmod_rit;
|
|
||||||
for (lmod_rit = lmods.rbegin (); lmod_rit != lmods.rend (); ++lmod_rit)
|
|
||||||
{
|
|
||||||
Taskmod tmod_l = *lmod_rit;
|
|
||||||
std::string uuid = tmod_l.getUuid ();
|
|
||||||
|
|
||||||
DEBUG_STR (" left uuid: " + uuid);
|
|
||||||
|
|
||||||
// skip if uuid had already been merged
|
|
||||||
if (uuid_left.find (uuid) == uuid_left.end ())
|
|
||||||
{
|
|
||||||
bool rwin = false;
|
|
||||||
bool lwin = false;
|
|
||||||
for (rmod_rit = rmods.rbegin (); rmod_rit != rmods.rend (); rmod_rit++)
|
|
||||||
{
|
|
||||||
Taskmod tmod_r = *rmod_rit;
|
|
||||||
|
|
||||||
DEBUG_STR (" right uuid: " + tmod_r.getUuid ());
|
|
||||||
if (tmod_r.getUuid () == uuid)
|
|
||||||
{
|
|
||||||
DEBUG_STR (" uuid match found for " + uuid);
|
|
||||||
|
|
||||||
// we already decided to take the mods from the right side
|
|
||||||
// but we have to find the first modification newer than
|
|
||||||
// the one on the left side to merge the history too
|
|
||||||
if (rwin)
|
|
||||||
{
|
|
||||||
DEBUG_STR (" scanning right side");
|
|
||||||
if (tmod_r > tmod_l)
|
|
||||||
mods.push_front (tmod_r);
|
|
||||||
|
|
||||||
std::list<Taskmod>::iterator tmp_it = rmod_rit.base ();
|
|
||||||
rmods.erase (--tmp_it);
|
|
||||||
rmod_rit--;
|
|
||||||
}
|
|
||||||
else if (lwin)
|
|
||||||
{
|
|
||||||
DEBUG_STR (" cleaning up right side");
|
|
||||||
|
|
||||||
// add tmod_r to local history
|
|
||||||
mods_history.push_front (tmod_r);
|
|
||||||
|
|
||||||
std::list<Taskmod>::iterator tmp_it = rmod_rit.base ();
|
|
||||||
rmods.erase (--tmp_it);
|
|
||||||
rmod_rit--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// which one is newer?
|
|
||||||
if (tmod_r > tmod_l)
|
|
||||||
{
|
|
||||||
std::cout << "Found remote change to "
|
|
||||||
<< (context.color () ? colorChanged.colorize (uuid) : uuid)
|
|
||||||
<< " \"" << cutOff (tmod_r.getBefore ().get ("description"), 10) << "\""
|
|
||||||
<< "\n";
|
|
||||||
|
|
||||||
mods.push_front(tmod_r);
|
|
||||||
|
|
||||||
// delete tmod from right side
|
|
||||||
std::list<Taskmod>::iterator tmp_it = rmod_rit.base ();
|
|
||||||
rmods.erase (--tmp_it);
|
|
||||||
rmod_rit--;
|
|
||||||
|
|
||||||
rwin = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::cout << "Retaining local changes to "
|
|
||||||
<< (context.color () ? colorRejected.colorize (uuid) : uuid)
|
|
||||||
<< " \"" << cutOff (tmod_l.getBefore ().get ("description"), 10) << "\""
|
|
||||||
<< "\n";
|
|
||||||
|
|
||||||
// inserting right mod into history of local database
|
|
||||||
// so that it can be restored later
|
|
||||||
// AND more important: create a history that looks the same
|
|
||||||
// as if we switched the roles 'remote' and 'local'
|
|
||||||
|
|
||||||
// thus we have to find the oldest change on the local branch that is not on remote branch
|
|
||||||
std::list<Taskmod>::iterator lmod_it;
|
|
||||||
std::list<Taskmod>::iterator last = lmod_it;
|
|
||||||
for (lmod_it = lmods.begin (); lmod_it != lmods.end (); ++lmod_it) {
|
|
||||||
if ((*lmod_it).getUuid () == uuid) {
|
|
||||||
last = lmod_it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tmod_l > tmod_r) { // local change is newer
|
|
||||||
last->setBefore(tmod_r.getAfter ());
|
|
||||||
|
|
||||||
// add tmod_r to local history
|
|
||||||
lmods.push_back(tmod_r);
|
|
||||||
}
|
|
||||||
else { // both mods have equal timestamps
|
|
||||||
// in this case the local branch wins as above, but the remote change with the
|
|
||||||
// same timestamp will be discarded
|
|
||||||
|
|
||||||
// find next (i.e. older) mod of this uuid on remote side
|
|
||||||
std::list<Taskmod>::reverse_iterator rmod_rit2;
|
|
||||||
for (rmod_rit2 = rmod_rit, ++rmod_rit2; rmod_rit2 != rmods.rend (); ++rmod_rit2) {
|
|
||||||
Taskmod tmp_mod = *rmod_rit2;
|
|
||||||
if (tmp_mod.getUuid () == uuid) {
|
|
||||||
last->setBefore (tmp_mod.getAfter ());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO feature: restore command? We would have to add a marker to the undo.file.
|
|
||||||
|
|
||||||
// delete tmod from right side
|
|
||||||
std::list<Taskmod>::iterator tmp_it = rmod_rit.base ();
|
|
||||||
rmods.erase (--tmp_it);
|
|
||||||
rmod_rit--;
|
|
||||||
|
|
||||||
// mark this uuid as merged
|
|
||||||
uuid_left.insert (uuid);
|
|
||||||
lwin = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // for
|
|
||||||
|
|
||||||
if (rwin)
|
|
||||||
{
|
|
||||||
DEBUG_STR (" concat the first match to left branch");
|
|
||||||
// concat the oldest (but still newer) modification on the right
|
|
||||||
// to the endpoint on the left
|
|
||||||
mods.front ().setBefore(tmod_l.getAfter ());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // for
|
|
||||||
|
|
||||||
DEBUG_STR ("adding non-conflicting changes from the right branch");
|
|
||||||
mods.splice (mods.begin (), rmods);
|
|
||||||
|
|
||||||
DEBUG_STR ("sorting taskmod list");
|
|
||||||
mods.sort ();
|
|
||||||
mods_history.sort ();
|
|
||||||
}
|
|
||||||
else if (rit == r.end ())
|
|
||||||
{
|
|
||||||
// nothing happend on the remote branch
|
|
||||||
// local branch is up-to-date
|
|
||||||
|
|
||||||
// nothing happend on the local branch either
|
|
||||||
|
|
||||||
// break, to suppress autopush
|
|
||||||
if (lit == l.end ())
|
|
||||||
{
|
|
||||||
mods.clear ();
|
|
||||||
lmods.clear ();
|
|
||||||
throw std::string ("Database is up-to-date, no merge required.");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else // lit == l.end ()
|
|
||||||
{
|
|
||||||
// nothing happend on the local branch
|
|
||||||
/*
|
|
||||||
std::cout << "No local changes detected.\n";
|
|
||||||
*/
|
|
||||||
|
|
||||||
// add remaining lines (remote branch) to the list of modifications
|
|
||||||
/*
|
|
||||||
std::cout << "Remote changes detected.\n";
|
|
||||||
*/
|
|
||||||
readTaskmods (r, rit, mods);
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////
|
|
||||||
// Now apply the changes.
|
|
||||||
// redo command:
|
|
||||||
|
|
||||||
if (!mods.empty ())
|
|
||||||
{
|
|
||||||
std::string pendingFile = location._data + "/pending.data";
|
|
||||||
std::vector <std::string> pending;
|
|
||||||
|
|
||||||
std::string completedFile = location._data + "/completed.data";
|
|
||||||
std::vector <std::string> completed;
|
|
||||||
|
|
||||||
if (! File::read (pendingFile, pending))
|
|
||||||
throw std::string ("Could not read '") + pendingFile + "'.";
|
|
||||||
|
|
||||||
if (! File::read (completedFile, completed))
|
|
||||||
throw std::string ("Could not read '") + completedFile + "'.";
|
|
||||||
|
|
||||||
// iterate over taskmod list
|
|
||||||
std::list<Taskmod>::iterator it;
|
|
||||||
for (it = mods.begin (); it != mods.end (); )
|
|
||||||
{
|
|
||||||
std::list<Taskmod>::iterator current = it++;
|
|
||||||
Taskmod tmod = *current;
|
|
||||||
|
|
||||||
// Modification to an existing task.
|
|
||||||
if (!tmod.isNew ())
|
|
||||||
{
|
|
||||||
std::string uuid = tmod.getUuid ();
|
|
||||||
Task::status statusBefore = tmod.getBefore().getStatus ();
|
|
||||||
Task::status statusAfter = tmod.getAfter().getStatus ();
|
|
||||||
|
|
||||||
std::vector <std::string>::iterator it;
|
|
||||||
|
|
||||||
bool found = false;
|
|
||||||
if ( (statusBefore == Task::completed)
|
|
||||||
|| (statusBefore == Task::deleted) )
|
|
||||||
{
|
|
||||||
// Find the same uuid in completed data
|
|
||||||
for (it = completed.begin (); it != completed.end (); ++it)
|
|
||||||
{
|
|
||||||
if (it->find ("uuid:\"" + uuid) != std::string::npos)
|
|
||||||
{
|
|
||||||
// Update the completed record.
|
|
||||||
/*
|
|
||||||
std::cout << "Modifying "
|
|
||||||
<< (context.color () ? colorChanged.colorize (uuid) : uuid)
|
|
||||||
<< "\n";
|
|
||||||
*/
|
|
||||||
|
|
||||||
// remove the \n from composeF4() string
|
|
||||||
std::string newline = tmod.getAfter ().composeF4 ();
|
|
||||||
newline = newline.substr (0, newline.length ()-1);
|
|
||||||
|
|
||||||
// does the tasks move to pending data?
|
|
||||||
// this taskmod will not arise from
|
|
||||||
// normal usage of task, but those kinds of
|
|
||||||
// taskmods may be constructed to merge databases
|
|
||||||
if ( (statusAfter != Task::completed)
|
|
||||||
&& (statusAfter != Task::deleted) )
|
|
||||||
{
|
|
||||||
// insert task into pending data
|
|
||||||
pending.push_back (newline);
|
|
||||||
|
|
||||||
// remove task from completed data
|
|
||||||
completed.erase (it);
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// replace the current line
|
|
||||||
*it = newline;
|
|
||||||
}
|
|
||||||
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Find the same uuid in the pending data.
|
|
||||||
for (it = pending.begin (); it != pending.end (); ++it)
|
|
||||||
{
|
|
||||||
if (it->find ("uuid:\"" + uuid) != std::string::npos)
|
|
||||||
{
|
|
||||||
// Update the pending record.
|
|
||||||
std::cout << "Found remote change to "
|
|
||||||
<< (context.color () ? colorChanged.colorize (uuid) : uuid)
|
|
||||||
<< " \"" << cutOff (tmod.getBefore ().get ("description"), 10) << "\""
|
|
||||||
<< "\n";
|
|
||||||
|
|
||||||
// remove the \n from composeF4() string
|
|
||||||
// which will replace the current line
|
|
||||||
std::string newline = tmod.getAfter ().composeF4 ();
|
|
||||||
newline = newline.substr (0, newline.length ()-1);
|
|
||||||
|
|
||||||
// does the tasks move to completed data
|
|
||||||
if ( (statusAfter == Task::completed)
|
|
||||||
|| (statusAfter == Task::deleted) )
|
|
||||||
{
|
|
||||||
// insert task into completed data
|
|
||||||
completed.push_back (newline);
|
|
||||||
|
|
||||||
// remove task from pending data
|
|
||||||
pending.erase (it);
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// replace the current line
|
|
||||||
*it = newline;
|
|
||||||
}
|
|
||||||
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
{
|
|
||||||
std::cout << "Missing "
|
|
||||||
<< (context.color () ? colorRejected.colorize (uuid) : uuid)
|
|
||||||
<< " \"" << cutOff (tmod.getBefore ().get ("description"), 10) << "\""
|
|
||||||
<< "\n";
|
|
||||||
mods.erase (current);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Check for dups.
|
|
||||||
std::string uuid = tmod.getAfter ().get ("uuid");
|
|
||||||
|
|
||||||
// Find the same uuid in the pending data.
|
|
||||||
bool found = false;
|
|
||||||
std::vector <std::string>::iterator pit;
|
|
||||||
for (pit = pending.begin (); pit != pending.end (); ++pit)
|
|
||||||
{
|
|
||||||
if (pit->find ("uuid:\"" + uuid) != std::string::npos)
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
{
|
|
||||||
std::cout << "Merging new remote task "
|
|
||||||
<< (context.color () ? colorAdded.colorize (uuid) : uuid)
|
|
||||||
<< " \"" << cutOff (tmod.getAfter ().get ("description"), 10) << "\""
|
|
||||||
<< "\n";
|
|
||||||
|
|
||||||
// remove the \n from composeF4() string
|
|
||||||
std::string newline = tmod.getAfter ().composeF4 ();
|
|
||||||
newline = newline.substr (0, newline.length ()-1);
|
|
||||||
pending.push_back (newline);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mods.erase (current);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// write pending file
|
|
||||||
if (! File::write (pendingFile, pending))
|
|
||||||
throw std::string ("Could not write '") + pendingFile + "'.";
|
|
||||||
|
|
||||||
// write completed file
|
|
||||||
if (! File::write (completedFile, completed))
|
|
||||||
throw std::string ("Could not write '") + completedFile + "'.";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mods.empty() || !lmods.empty() || !mods_history.empty()) {
|
|
||||||
// at this point undo contains the lines up to the branch-off point
|
|
||||||
// now we merge mods (new modifications from mergefile)
|
|
||||||
// with lmods (part of old undo.data)
|
|
||||||
lmods.sort();
|
|
||||||
mods.merge (lmods);
|
|
||||||
mods.merge (mods_history);
|
|
||||||
|
|
||||||
// generate undo.data format
|
|
||||||
std::list<Taskmod>::iterator it;
|
|
||||||
for (it = mods.begin (); it != mods.end (); it++)
|
|
||||||
undo.push_back(it->toString ());
|
|
||||||
|
|
||||||
// write undo file
|
|
||||||
if (! File::write (undoFile, undo, false))
|
|
||||||
throw std::string ("Could not write '") + undoFile + "'.";
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete objects
|
|
||||||
lmods.clear ();
|
|
||||||
mods.clear ();
|
|
||||||
mods_history.clear ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
611
src/TDB2.cpp
611
src/TDB2.cpp
|
@ -26,7 +26,10 @@
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#include <iostream> // TODO Remove.
|
#include <iostream> // TODO Remove.
|
||||||
|
#include <sstream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <list>
|
||||||
|
#include <set>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <Context.h>
|
#include <Context.h>
|
||||||
#include <Color.h>
|
#include <Color.h>
|
||||||
|
@ -38,6 +41,22 @@
|
||||||
|
|
||||||
extern Context context;
|
extern Context context;
|
||||||
|
|
||||||
|
#define NDEBUG
|
||||||
|
#include <assert.h>
|
||||||
|
#include <Taskmod.h>
|
||||||
|
|
||||||
|
#define DEBUG_OUTPUT 0
|
||||||
|
|
||||||
|
#if DEBUG_OUTPUT > 0
|
||||||
|
#define DEBUG_STR(str) std::cout << "DEBUG: " << str << "\n"; std::cout.flush()
|
||||||
|
#define DEBUG_STR_PART(str) std::cout << "DEBUG: " << str; std::cout.flush()
|
||||||
|
#define DEBUG_STR_END(str) std::cout << str << "\n"; std::cout.flush()
|
||||||
|
#else
|
||||||
|
#define DEBUG_STR(str)
|
||||||
|
#define DEBUG_STR_PART(str)
|
||||||
|
#define DEBUG_STR_END(str)
|
||||||
|
#endif
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
TF2::TF2 ()
|
TF2::TF2 ()
|
||||||
: _read_only (false)
|
: _read_only (false)
|
||||||
|
@ -577,6 +596,598 @@ void TDB2::synch ()
|
||||||
context.timer_synch.stop ();
|
context.timer_synch.stop ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Helper function for TDB::merge
|
||||||
|
void readTaskmods (std::vector <std::string> &input,
|
||||||
|
std::vector <std::string>::iterator &start,
|
||||||
|
std::list<Taskmod> &list)
|
||||||
|
{
|
||||||
|
std::string line;
|
||||||
|
Taskmod tmod_tmp;
|
||||||
|
|
||||||
|
DEBUG_STR ("reading taskmods from file: ");
|
||||||
|
|
||||||
|
for ( ; start != input.end (); ++start)
|
||||||
|
{
|
||||||
|
line = *start;
|
||||||
|
|
||||||
|
if (line.substr (0, 4) == "time")
|
||||||
|
{
|
||||||
|
std::stringstream stream (line.substr (5));
|
||||||
|
long ts;
|
||||||
|
stream >> ts;
|
||||||
|
|
||||||
|
if (stream.fail ())
|
||||||
|
throw std::string ("There was a problem reading the timestamp from the undo.data file.");
|
||||||
|
|
||||||
|
// 'time' is the first line of a modification
|
||||||
|
// thus we will (re)set the taskmod object
|
||||||
|
tmod_tmp.reset (ts);
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (line.substr (0, 3) == "old")
|
||||||
|
{
|
||||||
|
tmod_tmp.setBefore (Task (line.substr (4)));
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (line.substr (0, 3) == "new")
|
||||||
|
{
|
||||||
|
tmod_tmp.setAfter (Task (line.substr (4)));
|
||||||
|
|
||||||
|
// 'new' is the last line of a modification,
|
||||||
|
// thus we can push to the list
|
||||||
|
list.push_back (tmod_tmp);
|
||||||
|
|
||||||
|
assert (tmod_tmp.isValid ());
|
||||||
|
DEBUG_STR (" taskmod complete");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_STR ("DONE");
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void TDB2::merge (const std::string& mergeFile)
|
||||||
|
{
|
||||||
|
///////////////////////////////////////
|
||||||
|
// Copyright 2010 - 2011, Johannes Schlatow.
|
||||||
|
///////////////////////////////////////
|
||||||
|
|
||||||
|
// list of modifications that we want to add to the local database
|
||||||
|
std::list<Taskmod> mods;
|
||||||
|
|
||||||
|
// list of modifications that we want to add to the local history
|
||||||
|
std::list<Taskmod> mods_history;
|
||||||
|
|
||||||
|
// list of modifications on the local database
|
||||||
|
// has to be merged with mods to create the new undo.data
|
||||||
|
std::list<Taskmod> lmods;
|
||||||
|
|
||||||
|
// will contain the NEW undo.data
|
||||||
|
std::vector <std::string> undo_lines;
|
||||||
|
|
||||||
|
///////////////////////////////////////
|
||||||
|
// initialize the files:
|
||||||
|
|
||||||
|
// load merge file (undo file of right/remote branch)
|
||||||
|
std::vector <std::string> r;
|
||||||
|
if (! File::read (mergeFile, r))
|
||||||
|
throw std::string ("Could not read '") + mergeFile + "'.";
|
||||||
|
|
||||||
|
// file has to contain at least one entry
|
||||||
|
if (r.size () < 3)
|
||||||
|
throw std::string ("There are no changes to merge.");
|
||||||
|
|
||||||
|
// load undo file (left/local branch)
|
||||||
|
std::vector <std::string> l;
|
||||||
|
if (! File::read (undo._file._data, l))
|
||||||
|
throw std::string ("Could not read '") + undo._file._data + "'.";
|
||||||
|
|
||||||
|
std::string rline, lline;
|
||||||
|
std::vector <std::string>::iterator rit, lit;
|
||||||
|
|
||||||
|
// read first line
|
||||||
|
rit = r.begin ();
|
||||||
|
lit = l.begin ();
|
||||||
|
|
||||||
|
if (rit != r.end())
|
||||||
|
rline = *rit;
|
||||||
|
if (lit != l.end())
|
||||||
|
lline = *lit;
|
||||||
|
|
||||||
|
///////////////////////////////////////
|
||||||
|
// find the branch-off point:
|
||||||
|
|
||||||
|
// first lines are not equal => assuming mergeFile starts at a
|
||||||
|
// later point in time
|
||||||
|
if (lline.compare (rline) != 0)
|
||||||
|
{
|
||||||
|
// iterate in local file to find rline
|
||||||
|
for ( ; lit != l.end (); ++lit)
|
||||||
|
{
|
||||||
|
lline = *lit;
|
||||||
|
|
||||||
|
// push the line to the new undo.data
|
||||||
|
undo_lines.push_back (lline + "\n");
|
||||||
|
|
||||||
|
// found first matching lines?
|
||||||
|
if (lline.compare (rline) == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add some color.
|
||||||
|
Color colorAdded (context.config.get ("color.sync.added"));
|
||||||
|
Color colorChanged (context.config.get ("color.sync.changed"));
|
||||||
|
Color colorRejected (context.config.get ("color.sync.rejected"));
|
||||||
|
|
||||||
|
// at this point we can assume: (lline==rline) || (lit == l.end())
|
||||||
|
// thus we search for the first non-equal lines or the EOF
|
||||||
|
bool found = false;
|
||||||
|
for ( ; (lit != l.end ()) && (rit != r.end ()); ++lit, ++rit)
|
||||||
|
{
|
||||||
|
lline = *lit;
|
||||||
|
rline = *rit;
|
||||||
|
|
||||||
|
// found first non-matching lines?
|
||||||
|
if (lline.compare (rline) != 0)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// push the line to the new undo.data
|
||||||
|
undo_lines.push_back (lline + "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\n";
|
||||||
|
|
||||||
|
///////////////////////////////////////
|
||||||
|
// branch-off point found:
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
DEBUG_STR_PART ("Branch-off point found at: ");
|
||||||
|
DEBUG_STR_END (lline);
|
||||||
|
|
||||||
|
std::list<Taskmod> rmods;
|
||||||
|
|
||||||
|
// helper lists
|
||||||
|
std::set<std::string> uuid_new, uuid_left;
|
||||||
|
|
||||||
|
// 1. read taskmods out of the remaining lines
|
||||||
|
readTaskmods (l, lit, lmods);
|
||||||
|
readTaskmods (r, rit, rmods);
|
||||||
|
|
||||||
|
// 2. move new uuids into mods
|
||||||
|
DEBUG_STR_PART ("adding new uuids (left) to skip list...");
|
||||||
|
|
||||||
|
// modifications on the left side are already in the database
|
||||||
|
// we just need them to merge conflicts, so we add new the mods for
|
||||||
|
// new uuids to the skip-list 'uuid_left'
|
||||||
|
std::list<Taskmod>::iterator lmod_it;
|
||||||
|
for (lmod_it = lmods.begin (); lmod_it != lmods.end (); lmod_it++)
|
||||||
|
{
|
||||||
|
if (lmod_it->isNew ())
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
std::cout << "New local task "
|
||||||
|
<< (context.color () ? colorAdded.colorize (lmod_it->getUuid ()) : lmod_it->getUuid ())
|
||||||
|
<< "\n";
|
||||||
|
*/
|
||||||
|
|
||||||
|
uuid_left.insert (lmod_it->getUuid ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_STR_END ("done");
|
||||||
|
DEBUG_STR_PART ("move new uuids (right) to redo list...");
|
||||||
|
|
||||||
|
// new items on the right side need to be inserted into the
|
||||||
|
// local database
|
||||||
|
std::list<Taskmod>::iterator rmod_it;
|
||||||
|
for (rmod_it = rmods.begin (); rmod_it != rmods.end (); )
|
||||||
|
{
|
||||||
|
// we have to save and increment the iterator because we may want to delete
|
||||||
|
// the object from the list
|
||||||
|
std::list<Taskmod>::iterator current = rmod_it++;
|
||||||
|
Taskmod tmod = *current;
|
||||||
|
|
||||||
|
// new uuid?
|
||||||
|
if (tmod.isNew ())
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
std::cout << "Adding new remote task "
|
||||||
|
<< (context.color () ? colorAdded.colorize (tmod.getUuid ()) : tmod.getUuid ())
|
||||||
|
<< "\n";
|
||||||
|
*/
|
||||||
|
|
||||||
|
uuid_new.insert (tmod.getUuid ());
|
||||||
|
mods.push_back (tmod);
|
||||||
|
rmods.erase (current);
|
||||||
|
}
|
||||||
|
else if (uuid_new.find (tmod.getUuid ()) != uuid_new.end ())
|
||||||
|
{
|
||||||
|
// uuid of modification was new
|
||||||
|
mods.push_back (tmod);
|
||||||
|
rmods.erase (current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_STR_END ("done");
|
||||||
|
|
||||||
|
///////////////////////////////////////
|
||||||
|
// merge modifications:
|
||||||
|
DEBUG_STR ("Merging modifications:");
|
||||||
|
|
||||||
|
// we iterate backwards to resolve conflicts by timestamps (newest one wins)
|
||||||
|
std::list<Taskmod>::reverse_iterator lmod_rit;
|
||||||
|
std::list<Taskmod>::reverse_iterator rmod_rit;
|
||||||
|
for (lmod_rit = lmods.rbegin (); lmod_rit != lmods.rend (); ++lmod_rit)
|
||||||
|
{
|
||||||
|
Taskmod tmod_l = *lmod_rit;
|
||||||
|
std::string uuid = tmod_l.getUuid ();
|
||||||
|
|
||||||
|
DEBUG_STR (" left uuid: " + uuid);
|
||||||
|
|
||||||
|
// skip if uuid had already been merged
|
||||||
|
if (uuid_left.find (uuid) == uuid_left.end ())
|
||||||
|
{
|
||||||
|
bool rwin = false;
|
||||||
|
bool lwin = false;
|
||||||
|
for (rmod_rit = rmods.rbegin (); rmod_rit != rmods.rend (); rmod_rit++)
|
||||||
|
{
|
||||||
|
Taskmod tmod_r = *rmod_rit;
|
||||||
|
|
||||||
|
DEBUG_STR (" right uuid: " + tmod_r.getUuid ());
|
||||||
|
if (tmod_r.getUuid () == uuid)
|
||||||
|
{
|
||||||
|
DEBUG_STR (" uuid match found for " + uuid);
|
||||||
|
|
||||||
|
// we already decided to take the mods from the right side
|
||||||
|
// but we have to find the first modification newer than
|
||||||
|
// the one on the left side to merge the history too
|
||||||
|
if (rwin)
|
||||||
|
{
|
||||||
|
DEBUG_STR (" scanning right side");
|
||||||
|
if (tmod_r > tmod_l)
|
||||||
|
mods.push_front (tmod_r);
|
||||||
|
|
||||||
|
std::list<Taskmod>::iterator tmp_it = rmod_rit.base ();
|
||||||
|
rmods.erase (--tmp_it);
|
||||||
|
rmod_rit--;
|
||||||
|
}
|
||||||
|
else if (lwin)
|
||||||
|
{
|
||||||
|
DEBUG_STR (" cleaning up right side");
|
||||||
|
|
||||||
|
// add tmod_r to local history
|
||||||
|
mods_history.push_front (tmod_r);
|
||||||
|
|
||||||
|
std::list<Taskmod>::iterator tmp_it = rmod_rit.base ();
|
||||||
|
rmods.erase (--tmp_it);
|
||||||
|
rmod_rit--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// which one is newer?
|
||||||
|
if (tmod_r > tmod_l)
|
||||||
|
{
|
||||||
|
std::cout << "Found remote change to "
|
||||||
|
<< (context.color () ? colorChanged.colorize (uuid) : uuid)
|
||||||
|
<< " \"" << cutOff (tmod_r.getBefore ().get ("description"), 10) << "\""
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
mods.push_front(tmod_r);
|
||||||
|
|
||||||
|
// delete tmod from right side
|
||||||
|
std::list<Taskmod>::iterator tmp_it = rmod_rit.base ();
|
||||||
|
rmods.erase (--tmp_it);
|
||||||
|
rmod_rit--;
|
||||||
|
|
||||||
|
rwin = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout << "Retaining local changes to "
|
||||||
|
<< (context.color () ? colorRejected.colorize (uuid) : uuid)
|
||||||
|
<< " \"" << cutOff (tmod_l.getBefore ().get ("description"), 10) << "\""
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
// inserting right mod into history of local database
|
||||||
|
// so that it can be restored later
|
||||||
|
// AND more important: create a history that looks the same
|
||||||
|
// as if we switched the roles 'remote' and 'local'
|
||||||
|
|
||||||
|
// thus we have to find the oldest change on the local branch that is not on remote branch
|
||||||
|
std::list<Taskmod>::iterator lmod_it;
|
||||||
|
std::list<Taskmod>::iterator last = lmod_it;
|
||||||
|
for (lmod_it = lmods.begin (); lmod_it != lmods.end (); ++lmod_it) {
|
||||||
|
if ((*lmod_it).getUuid () == uuid) {
|
||||||
|
last = lmod_it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tmod_l > tmod_r) { // local change is newer
|
||||||
|
last->setBefore(tmod_r.getAfter ());
|
||||||
|
|
||||||
|
// add tmod_r to local history
|
||||||
|
lmods.push_back(tmod_r);
|
||||||
|
}
|
||||||
|
else { // both mods have equal timestamps
|
||||||
|
// in this case the local branch wins as above, but the remote change with the
|
||||||
|
// same timestamp will be discarded
|
||||||
|
|
||||||
|
// find next (i.e. older) mod of this uuid on remote side
|
||||||
|
std::list<Taskmod>::reverse_iterator rmod_rit2;
|
||||||
|
for (rmod_rit2 = rmod_rit, ++rmod_rit2; rmod_rit2 != rmods.rend (); ++rmod_rit2) {
|
||||||
|
Taskmod tmp_mod = *rmod_rit2;
|
||||||
|
if (tmp_mod.getUuid () == uuid) {
|
||||||
|
last->setBefore (tmp_mod.getAfter ());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO feature: restore command? We would have to add a marker to the undo.file.
|
||||||
|
|
||||||
|
// delete tmod from right side
|
||||||
|
std::list<Taskmod>::iterator tmp_it = rmod_rit.base ();
|
||||||
|
rmods.erase (--tmp_it);
|
||||||
|
rmod_rit--;
|
||||||
|
|
||||||
|
// mark this uuid as merged
|
||||||
|
uuid_left.insert (uuid);
|
||||||
|
lwin = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // for
|
||||||
|
|
||||||
|
if (rwin)
|
||||||
|
{
|
||||||
|
DEBUG_STR (" concat the first match to left branch");
|
||||||
|
// concat the oldest (but still newer) modification on the right
|
||||||
|
// to the endpoint on the left
|
||||||
|
mods.front ().setBefore(tmod_l.getAfter ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // for
|
||||||
|
|
||||||
|
DEBUG_STR ("adding non-conflicting changes from the right branch");
|
||||||
|
mods.splice (mods.begin (), rmods);
|
||||||
|
|
||||||
|
DEBUG_STR ("sorting taskmod list");
|
||||||
|
mods.sort ();
|
||||||
|
mods_history.sort ();
|
||||||
|
}
|
||||||
|
else if (rit == r.end ())
|
||||||
|
{
|
||||||
|
// nothing happend on the remote branch
|
||||||
|
// local branch is up-to-date
|
||||||
|
|
||||||
|
// nothing happend on the local branch either
|
||||||
|
|
||||||
|
// break, to suppress autopush
|
||||||
|
if (lit == l.end ())
|
||||||
|
{
|
||||||
|
mods.clear ();
|
||||||
|
lmods.clear ();
|
||||||
|
throw std::string ("Database is up-to-date, no merge required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else // lit == l.end ()
|
||||||
|
{
|
||||||
|
// nothing happend on the local branch
|
||||||
|
/*
|
||||||
|
std::cout << "No local changes detected.\n";
|
||||||
|
*/
|
||||||
|
|
||||||
|
// add remaining lines (remote branch) to the list of modifications
|
||||||
|
/*
|
||||||
|
std::cout << "Remote changes detected.\n";
|
||||||
|
*/
|
||||||
|
readTaskmods (r, rit, mods);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////
|
||||||
|
// Now apply the changes.
|
||||||
|
// redo command:
|
||||||
|
|
||||||
|
if (!mods.empty ())
|
||||||
|
{
|
||||||
|
std::vector <std::string> pending_lines;
|
||||||
|
|
||||||
|
std::vector <std::string> completed_lines;
|
||||||
|
|
||||||
|
if (! File::read (pending._file._data, pending_lines))
|
||||||
|
throw std::string ("Could not read '") + pending._file._data + "'.";
|
||||||
|
|
||||||
|
if (! File::read (completed._file._data, completed_lines))
|
||||||
|
throw std::string ("Could not read '") + completed._file._data + "'.";
|
||||||
|
|
||||||
|
// iterate over taskmod list
|
||||||
|
std::list<Taskmod>::iterator it;
|
||||||
|
for (it = mods.begin (); it != mods.end (); )
|
||||||
|
{
|
||||||
|
std::list<Taskmod>::iterator current = it++;
|
||||||
|
Taskmod tmod = *current;
|
||||||
|
|
||||||
|
// Modification to an existing task.
|
||||||
|
if (!tmod.isNew ())
|
||||||
|
{
|
||||||
|
std::string uuid = tmod.getUuid ();
|
||||||
|
Task::status statusBefore = tmod.getBefore().getStatus ();
|
||||||
|
Task::status statusAfter = tmod.getAfter().getStatus ();
|
||||||
|
|
||||||
|
std::vector <std::string>::iterator it;
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
if ( (statusBefore == Task::completed)
|
||||||
|
|| (statusBefore == Task::deleted) )
|
||||||
|
{
|
||||||
|
// Find the same uuid in completed data
|
||||||
|
for (it = completed_lines.begin (); it != completed_lines.end (); ++it)
|
||||||
|
{
|
||||||
|
if (it->find ("uuid:\"" + uuid) != std::string::npos)
|
||||||
|
{
|
||||||
|
// Update the completed record.
|
||||||
|
/*
|
||||||
|
std::cout << "Modifying "
|
||||||
|
<< (context.color () ? colorChanged.colorize (uuid) : uuid)
|
||||||
|
<< "\n";
|
||||||
|
*/
|
||||||
|
|
||||||
|
// remove the \n from composeF4() string
|
||||||
|
std::string newline = tmod.getAfter ().composeF4 ();
|
||||||
|
newline = newline.substr (0, newline.length ()-1);
|
||||||
|
|
||||||
|
// does the tasks move to pending data?
|
||||||
|
// this taskmod will not arise from
|
||||||
|
// normal usage of task, but those kinds of
|
||||||
|
// taskmods may be constructed to merge databases
|
||||||
|
if ( (statusAfter != Task::completed)
|
||||||
|
&& (statusAfter != Task::deleted) )
|
||||||
|
{
|
||||||
|
// insert task into pending data
|
||||||
|
pending_lines.push_back (newline);
|
||||||
|
|
||||||
|
// remove task from completed data
|
||||||
|
completed_lines.erase (it);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// replace the current line
|
||||||
|
*it = newline;
|
||||||
|
}
|
||||||
|
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Find the same uuid in the pending data.
|
||||||
|
for (it = pending_lines.begin (); it != pending_lines.end (); ++it)
|
||||||
|
{
|
||||||
|
if (it->find ("uuid:\"" + uuid) != std::string::npos)
|
||||||
|
{
|
||||||
|
// Update the pending record.
|
||||||
|
std::cout << "Found remote change to "
|
||||||
|
<< (context.color () ? colorChanged.colorize (uuid) : uuid)
|
||||||
|
<< " \"" << cutOff (tmod.getBefore ().get ("description"), 10) << "\""
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
// remove the \n from composeF4() string
|
||||||
|
// which will replace the current line
|
||||||
|
std::string newline = tmod.getAfter ().composeF4 ();
|
||||||
|
newline = newline.substr (0, newline.length ()-1);
|
||||||
|
|
||||||
|
// does the tasks move to completed data
|
||||||
|
if ( (statusAfter == Task::completed)
|
||||||
|
|| (statusAfter == Task::deleted) )
|
||||||
|
{
|
||||||
|
// insert task into completed data
|
||||||
|
completed_lines.push_back (newline);
|
||||||
|
|
||||||
|
// remove task from pending data
|
||||||
|
pending_lines.erase (it);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// replace the current line
|
||||||
|
*it = newline;
|
||||||
|
}
|
||||||
|
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
std::cout << "Missing "
|
||||||
|
<< (context.color () ? colorRejected.colorize (uuid) : uuid)
|
||||||
|
<< " \"" << cutOff (tmod.getBefore ().get ("description"), 10) << "\""
|
||||||
|
<< "\n";
|
||||||
|
mods.erase (current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Check for dups.
|
||||||
|
std::string uuid = tmod.getAfter ().get ("uuid");
|
||||||
|
|
||||||
|
// Find the same uuid in the pending data.
|
||||||
|
bool found = false;
|
||||||
|
std::vector <std::string>::iterator pit;
|
||||||
|
for (pit = pending_lines.begin (); pit != pending_lines.end (); ++pit)
|
||||||
|
{
|
||||||
|
if (pit->find ("uuid:\"" + uuid) != std::string::npos)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
std::cout << "Merging new remote task "
|
||||||
|
<< (context.color () ? colorAdded.colorize (uuid) : uuid)
|
||||||
|
<< " \"" << cutOff (tmod.getAfter ().get ("description"), 10) << "\""
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
// remove the \n from composeF4() string
|
||||||
|
std::string newline = tmod.getAfter ().composeF4 ();
|
||||||
|
newline = newline.substr (0, newline.length ()-1);
|
||||||
|
pending_lines.push_back (newline);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mods.erase (current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write pending file
|
||||||
|
if (! File::write (pending._file._data, pending_lines))
|
||||||
|
throw std::string ("Could not write '") + pending._file._data + "'.";
|
||||||
|
|
||||||
|
// write completed file
|
||||||
|
if (! File::write (completed._file._data, completed_lines))
|
||||||
|
throw std::string ("Could not write '") + completed._file._data + "'.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mods.empty() || !lmods.empty() || !mods_history.empty()) {
|
||||||
|
// at this point undo contains the lines up to the branch-off point
|
||||||
|
// now we merge mods (new modifications from mergefile)
|
||||||
|
// with lmods (part of old undo.data)
|
||||||
|
lmods.sort();
|
||||||
|
mods.merge (lmods);
|
||||||
|
mods.merge (mods_history);
|
||||||
|
|
||||||
|
// generate undo.data format
|
||||||
|
std::list<Taskmod>::iterator it;
|
||||||
|
for (it = mods.begin (); it != mods.end (); it++)
|
||||||
|
undo_lines.push_back(it->toString ());
|
||||||
|
|
||||||
|
// write undo file
|
||||||
|
if (! File::write (undo._file._data, undo_lines, false))
|
||||||
|
throw std::string ("Could not write '") + undo._file._data + "'.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete objects
|
||||||
|
lmods.clear ();
|
||||||
|
mods.clear ();
|
||||||
|
mods_history.clear ();
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
void TDB2::revert ()
|
void TDB2::revert ()
|
||||||
{
|
{
|
||||||
|
|
|
@ -97,6 +97,7 @@ public:
|
||||||
void modify (Task&);
|
void modify (Task&);
|
||||||
void commit ();
|
void commit ();
|
||||||
void synch ();
|
void synch ();
|
||||||
|
void merge (const std::string&);
|
||||||
void revert ();
|
void revert ();
|
||||||
int gc ();
|
int gc ();
|
||||||
int next_id ();
|
int next_id ();
|
||||||
|
|
|
@ -59,9 +59,9 @@ int CmdAdd::execute (std::string& output)
|
||||||
// TODO This should be a call in to feedback.cpp.
|
// TODO This should be a call in to feedback.cpp.
|
||||||
if (context.verbose ("new-id"))
|
if (context.verbose ("new-id"))
|
||||||
output = format (STRING_CMD_ADD_FEEDBACK, context.tdb2.next_id ()) + "\n";
|
output = format (STRING_CMD_ADD_FEEDBACK, context.tdb2.next_id ()) + "\n";
|
||||||
/*
|
|
||||||
context.footnote (onProjectChange (task));
|
// context.footnote (onProjectChange (task));
|
||||||
*/
|
|
||||||
context.tdb2.commit ();
|
context.tdb2.commit ();
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,9 +82,7 @@ int CmdMerge::execute (std::string& output)
|
||||||
else
|
else
|
||||||
file = uri._path;
|
file = uri._path;
|
||||||
|
|
||||||
context.tdb.lock (context.config.getBoolean ("locking"));
|
context.tdb2.merge (file);
|
||||||
context.tdb.merge (file);
|
|
||||||
context.tdb.unlock ();
|
|
||||||
|
|
||||||
output += "Merge complete.\n";
|
output += "Merge complete.\n";
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,9 @@
|
||||||
|
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
use Test::More tests => 53;
|
use Test::More tests => 31;
|
||||||
use File::Copy;
|
use File::Copy;
|
||||||
|
use File::Path;
|
||||||
|
|
||||||
use constant false => 0;
|
use constant false => 0;
|
||||||
use constant true => 1;
|
use constant true => 1;
|
||||||
|
@ -175,65 +176,50 @@ like ($output, qr/up-to-date/, "res2: up-to-date");
|
||||||
unlike ($output, qr/Missing/, "no missing entry");
|
unlike ($output, qr/Missing/, "no missing entry");
|
||||||
|
|
||||||
# Cleanup.
|
# Cleanup.
|
||||||
unlink 'data1/pending.data';
|
unlink qw(data1/pending.data data1/completed.data data1/undo.data data1/undo.save data1/backlog.data data1/synch.key 1.rc);
|
||||||
ok (!-r 'data1/pending.data', 'Removed data1/pending.data');
|
ok (! -r 'data1/pending.data' &&
|
||||||
unlink 'data1/completed.data';
|
! -r 'data1/completed.data' &&
|
||||||
ok (!-r 'data1/completed.data', 'Removed data1/completed.data');
|
! -r 'data1/undo.data' &&
|
||||||
unlink 'data1/undo.data';
|
! -r 'data1/undo.save' &&
|
||||||
ok (!-r 'data1/undo.data', 'Removed data1/undo.data');
|
! -r 'data1/backlog.data' &&
|
||||||
unlink 'data1/backlog.data';
|
! -r 'data1/synch_key.data' &&
|
||||||
ok (!-r 'data1/backlog.data', 'Removed data1/backlog.data');
|
! -r '1.rc', 'data1 Cleanup');
|
||||||
unlink 'data1/synch.key';
|
|
||||||
ok (!-r 'data1/synch.key', 'Removed data1/synch.key');
|
|
||||||
|
|
||||||
unlink 'data2/pending.data';
|
unlink qw(data2/pending.data data2/completed.data data2/undo.data data2/undo.save data2/backlog.data data2/synch.key 2.rc);
|
||||||
ok (!-r 'data2/pending.data', 'Removed data2/pending.data');
|
ok (! -r 'data2/pending.data' &&
|
||||||
unlink 'data2/completed.data';
|
! -r 'data2/completed.data' &&
|
||||||
ok (!-r 'data2/completed.data', 'Removed data2/completed.data');
|
! -r 'data2/undo.data' &&
|
||||||
unlink 'data2/undo.data';
|
! -r 'data2/undo.save' &&
|
||||||
ok (!-r 'data2/undo.data', 'Removed data2/undo.data');
|
! -r 'data2/backlog.data' &&
|
||||||
unlink 'data2/backlog.data';
|
! -r 'data2/synch_key.data' &&
|
||||||
ok (!-r 'data2/backlog.data', 'Removed data2/backlog.data');
|
! -r '2.rc', 'data2 Cleanup');
|
||||||
unlink 'data2/synch.key';
|
|
||||||
ok (!-r 'data2/synch.key', 'Removed data2/synch.key');
|
|
||||||
|
|
||||||
unlink 'data3/pending.data';
|
unlink qw(data3/pending.data data3/completed.data data3/undo.data data3/undo.save data3/backlog.data data3/synch.key 3.rc);
|
||||||
ok (!-r 'data3/pending.data', 'Removed data3/pending.data');
|
ok (! -r 'data3/pending.data' &&
|
||||||
unlink 'data3/completed.data';
|
! -r 'data3/completed.data' &&
|
||||||
ok (!-r 'data3/completed.data', 'Removed data3/completed.data');
|
! -r 'data3/undo.data' &&
|
||||||
unlink 'data3/undo.data';
|
! -r 'data3/undo.save' &&
|
||||||
ok (!-r 'data3/undo.data', 'Removed data3/undo.data');
|
! -r 'data3/backlog.data' &&
|
||||||
unlink 'data3/backlog.data';
|
! -r 'data3/synch_key.data' &&
|
||||||
ok (!-r 'data3/backlog.data', 'Removed data3/backlog.data');
|
! -r '3.rc', 'data3 Cleanup');
|
||||||
unlink 'data3/synch.key';
|
|
||||||
ok (!-r 'data3/synch.key', 'Removed data3/synch.key');
|
|
||||||
|
|
||||||
unlink 'backup/pending.data';
|
unlink qw(backup/pending.data backup/completed.data backup/undo.data backup/undo.save backup/backlog.data backup/synch.key);
|
||||||
ok (!-r 'backup/pending.data', 'Removed backup/pending.data');
|
ok (! -r 'backup/pending.data' &&
|
||||||
unlink 'backup/completed.data';
|
! -r 'backup/completed.data' &&
|
||||||
ok (!-r 'backup/completed.data', 'Removed backup/completed.data');
|
! -r 'backup/undo.data' &&
|
||||||
unlink 'backup/undo.data';
|
! -r 'backup/undo.save' &&
|
||||||
ok (!-r 'backup/undo.data', 'Removed backup/undo.data');
|
! -r 'backup/backlog.data' &&
|
||||||
unlink 'backup/backlog.data';
|
! -r 'backup/synch_key.data', 'backup Cleanup');
|
||||||
ok (!-r 'backup/backlog.data', 'Removed backup/backlog.data');
|
|
||||||
unlink 'backup/synch.key';
|
|
||||||
ok (!-r 'backup/synch.key', 'Removed backup/synch.key');
|
|
||||||
|
|
||||||
unlink '1.rc';
|
rmtree (['data1/extensions', 'data1', 'data2/extensions', 'data2', 'data3/extensions', 'data3', 'backup/extensions', 'backup'], 0, 1);
|
||||||
ok (!-r '1.rc', 'Removed 1.rc');
|
ok (! -e 'data1/extensions' &&
|
||||||
unlink '2.rc';
|
! -e 'data1' &&
|
||||||
ok (!-r '2.rc', 'Removed 2.rc');
|
! -e 'data2/extensions' &&
|
||||||
unlink '3.rc';
|
! -e 'data2' &&
|
||||||
ok (!-r '3.rc', 'Removed 3.rc');
|
! -e 'data3/extensions' &&
|
||||||
|
! -e 'data3' &&
|
||||||
rmdir("data1");
|
! -e 'backup/extensions' &&
|
||||||
ok (!-e "data1", "Removed dir data1");
|
! -e 'backup', 'Removed dir local');
|
||||||
rmdir("data2");
|
|
||||||
ok (!-e "data2", "Removed dir data2");
|
|
||||||
rmdir("data3");
|
|
||||||
ok (!-e "data3", "Removed dir data3");
|
|
||||||
rmdir("backup");
|
|
||||||
ok (!-e "backup", "Removed dir backup");
|
|
||||||
|
|
||||||
exit 0;
|
exit 0;
|
||||||
|
|
||||||
|
|
184
test/merge.t
184
test/merge.t
|
@ -28,8 +28,9 @@
|
||||||
|
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
use Test::More tests => 45;
|
use Test::More tests => 33;
|
||||||
use File::Copy;
|
use File::Copy;
|
||||||
|
use File::Path;
|
||||||
|
|
||||||
use constant false => 0;
|
use constant false => 0;
|
||||||
use constant true => 1;
|
use constant true => 1;
|
||||||
|
@ -45,12 +46,12 @@ if (open my $fh, '>', 'local.rc')
|
||||||
{
|
{
|
||||||
print $fh "data.location=./local\n",
|
print $fh "data.location=./local\n",
|
||||||
"confirmation=no\n",
|
"confirmation=no\n",
|
||||||
"merge.autopush=no\n",
|
"merge.autopush=no\n",
|
||||||
"report.list.description=DESC\n",
|
"report.list.description=DESC\n",
|
||||||
"report.list.columns=id,project,active,priority,description,tags\n",
|
"report.list.columns=id,project,active,priority,description,tags\n",
|
||||||
"report.list.labels=id,pro,a,pri,d,t\n",
|
"report.list.labels=id,pro,a,pri,d,t\n",
|
||||||
"report.list.sort=id+\n",
|
"report.list.sort=id+\n",
|
||||||
"report.list.filter=status:pending\n";
|
"report.list.filter=status:pending\n";
|
||||||
close $fh;
|
close $fh;
|
||||||
ok (-r 'local.rc', 'Created local.rc');
|
ok (-r 'local.rc', 'Created local.rc');
|
||||||
}
|
}
|
||||||
|
@ -59,12 +60,12 @@ if (open my $fh, '>', 'remote.rc')
|
||||||
{
|
{
|
||||||
print $fh "data.location=./remote\n",
|
print $fh "data.location=./remote\n",
|
||||||
"confirmation=no\n",
|
"confirmation=no\n",
|
||||||
"merge.autopush=no\n",
|
"merge.autopush=no\n",
|
||||||
"report.list.description=DESC\n",
|
"report.list.description=DESC\n",
|
||||||
"report.list.columns=id,project,active,priority,description,tags\n",
|
"report.list.columns=id,project,active,priority,description,tags\n",
|
||||||
"report.list.labels=id,pro,a,pri,d,t\n",
|
"report.list.labels=id,pro,a,pri,d,t\n",
|
||||||
"report.list.sort=id+\n",
|
"report.list.sort=id+\n",
|
||||||
"report.list.filter=status:pending\n";
|
"report.list.filter=status:pending\n";
|
||||||
close $fh;
|
close $fh;
|
||||||
ok (-r 'remote.rc', 'Created remote.rc');
|
ok (-r 'remote.rc', 'Created remote.rc');
|
||||||
}
|
}
|
||||||
|
@ -72,71 +73,71 @@ if (open my $fh, '>', 'remote.rc')
|
||||||
# Create some basic tasks on both sides
|
# Create some basic tasks on both sides
|
||||||
qx{../src/task rc:local.rc add left_modified};
|
qx{../src/task rc:local.rc add left_modified};
|
||||||
diag ("25 second delay");
|
diag ("25 second delay");
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:local.rc add right_modified};
|
qx{../src/task rc:local.rc add right_modified};
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:local.rc add left_newer};
|
qx{../src/task rc:local.rc add left_newer};
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:local.rc add right_newer};
|
qx{../src/task rc:local.rc add right_newer};
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:local.rc add left_deleted};
|
qx{../src/task rc:local.rc add left_deleted};
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:local.rc add right_deleted};
|
qx{../src/task rc:local.rc add right_deleted};
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:local.rc add left_completed};
|
qx{../src/task rc:local.rc add left_completed};
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:local.rc add right_completed};
|
qx{../src/task rc:local.rc add right_completed};
|
||||||
sleep(1);
|
sleep 1;
|
||||||
|
|
||||||
copy("local/undo.data", "remote/undo.data") or fail("copy local/undo.data to remote/undo.data");
|
copy ("local/undo.data", "remote/undo.data") or fail ("copy local/undo.data to remote/undo.data");
|
||||||
copy("local/pending.data", "remote/pending.data") or fail("copy local/undo.data to remote/undo.data");
|
copy ("local/pending.data", "remote/pending.data") or fail ("copy local/undo.data to remote/undo.data");
|
||||||
copy("local/completed.data", "remote/completed.data") or fail("copy local/undo.data to remote/undo.data");
|
copy ("local/completed.data", "remote/completed.data") or fail ("copy local/undo.data to remote/undo.data");
|
||||||
|
|
||||||
# make local modifications
|
# make local modifications
|
||||||
qx{../src/task rc:local.rc add left_added}; #left_added
|
qx{../src/task rc:local.rc add left_added}; #left_added
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:local.rc 1 modify prio:H}; #left_modified
|
qx{../src/task rc:local.rc 1 modify prio:H}; #left_modified
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:local.rc 3 modify +stay}; #left_newer
|
qx{../src/task rc:local.rc 3 modify +stay}; #left_newer
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:local.rc 4 modify project:test}; #right_newer
|
qx{../src/task rc:local.rc 4 modify project:test}; #right_newer
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:local.rc 6 modify +delete}; #right_deleted
|
qx{../src/task rc:local.rc 6 modify +delete}; #right_deleted
|
||||||
sleep(1);
|
sleep 1;
|
||||||
|
|
||||||
# make remote modifications
|
# make remote modifications
|
||||||
qx{../src/task rc:remote.rc add right_added}; #right_added
|
qx{../src/task rc:remote.rc add right_added}; #right_added
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:remote.rc 2 modify prio:L}; #right_modified
|
qx{../src/task rc:remote.rc 2 modify prio:L}; #right_modified
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:remote.rc 2 modify wait:tomorrow}; #right_modified
|
qx{../src/task rc:remote.rc 2 modify wait:tomorrow}; #right_modified
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:remote.rc 4 modify proj:realProject}; #right_newer
|
qx{../src/task rc:remote.rc 4 modify proj:realProject}; #right_newer
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:remote.rc 5 modify project:deletion}; #left_deleted
|
qx{../src/task rc:remote.rc 5 modify project:deletion}; #left_deleted
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:remote.rc 8 done}; #right_completed
|
qx{../src/task rc:remote.rc 8 done}; #right_completed
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:remote.rc 6 del}; #right_deleted
|
qx{../src/task rc:remote.rc 6 del}; #right_deleted
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:remote.rc 3 done}; #left_newer
|
qx{../src/task rc:remote.rc 3 done}; #left_newer
|
||||||
sleep(1);
|
sleep 1;
|
||||||
|
|
||||||
# make new local modifications
|
# make new local modifications
|
||||||
qx{../src/task rc:local.rc 3 start}; #left_newer
|
qx{../src/task rc:local.rc 3 start}; #left_newer
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:local.rc 4 modify +car}; #right_newer
|
qx{../src/task rc:local.rc 4 modify +car}; #right_newer
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:local.rc 7 done}; #left_completed
|
qx{../src/task rc:local.rc 7 done}; #left_completed
|
||||||
sleep(1);
|
sleep 1;
|
||||||
qx{../src/task rc:local.rc 5 del}; #left_deleted
|
qx{../src/task rc:local.rc 5 del}; #left_deleted
|
||||||
sleep(1);
|
sleep 1;
|
||||||
|
|
||||||
# make new remote modifications
|
# make new remote modifications
|
||||||
qx{../src/task rc:remote.rc 4 modify +gym}; # right_newer
|
qx{../src/task rc:remote.rc 4 modify +gym}; # right_newer
|
||||||
|
|
||||||
# merge remote into local
|
# merge remote into local
|
||||||
copy("local/undo.data", "local/undo.save") or fail("copy local/undo.data to local/undo.save");
|
copy ("local/undo.data", "local/undo.save") or fail ("copy local/undo.data to local/undo.save");
|
||||||
my $output_l = qx{../src/task rc:local.rc merge remote/};
|
my $output_l = qx{../src/task rc:local.rc merge remote/};
|
||||||
|
|
||||||
#check output
|
#check output
|
||||||
|
@ -151,37 +152,37 @@ unlike ($output_r, qr/Missing/, "remote-merge: no missing entry")
|
||||||
unlike ($output_r, qr/Not adding duplicate/, "remote-merge: no duplicates");
|
unlike ($output_r, qr/Not adding duplicate/, "remote-merge: no duplicates");
|
||||||
|
|
||||||
# check reports
|
# check reports
|
||||||
my $report_l = qx{../src/task rc:local.rc};
|
my $report_l = qx{../src/task rc:local.rc list};
|
||||||
my $report_r = qx{../src/task rc:remote.rc};
|
my $report_r = qx{../src/task rc:remote.rc list};
|
||||||
|
|
||||||
# local-merge
|
# local-merge
|
||||||
like ($report_l, qr/left_added/, "local-merge: left_added is present");
|
like ($report_l, qr/left_added/, "local-merge: left_added is present");
|
||||||
like ($report_l, qr/right_added/, "local-merge: right_added is present");
|
like ($report_l, qr/right_added/, "local-merge: right_added is present");
|
||||||
like ($report_l, qr/H.*left_modified/, "local-merge: left_modified ok");
|
like ($report_l, qr/H.*left_modified/, "local-merge: left_modified ok");
|
||||||
like ($report_l, qr/\*.*left_newer.*stay/, "local-merge: left_newer ok");
|
like ($report_l, qr/\*.*left_newer.*stay/, "local-merge: left_newer ok");
|
||||||
like ($report_l, qr/realProject.*right_newer.*gym/, "local-merge: right_newer ok");
|
like ($report_l, qr/realProject.*right_newer.*gym/, "local-merge: right_newer ok");
|
||||||
|
|
||||||
$report_l = qx{../src/task rc:local.rc export.csv};
|
$report_l = qx{../src/task rc:local.rc export};
|
||||||
like ($report_l, qr/deleted.*left_deleted/, "local-merge: left_deleted ok");
|
like ($report_l, qr/deleted.*left_deleted/, "local-merge: left_deleted ok");
|
||||||
like ($report_l, qr/deleted.*right_deleted/, "local-merge: right_deleted ok");
|
like ($report_l, qr/deleted.*right_deleted/, "local-merge: right_deleted ok");
|
||||||
like ($report_l, qr/completed.*left_completed/, "local-merge: left_completed ok");
|
like ($report_l, qr/completed.*left_completed/, "local-merge: left_completed ok");
|
||||||
like ($report_l, qr/completed.*right_completed/, "local-merge: right_completed ok");
|
like ($report_l, qr/completed.*right_completed/, "local-merge: right_completed ok");
|
||||||
|
|
||||||
$report_l = qx(../src/task rc:local.rc waiting);
|
$report_l = qx(../src/task rc:local.rc waiting);
|
||||||
like ($report_l, qr/L.*right_modified/, "local-merge: right_modified ok");
|
like ($report_l, qr/L.*right_modified/, "local-merge: right_modified ok");
|
||||||
|
|
||||||
# remote-merge
|
# remote-merge
|
||||||
like ($report_r, qr/left_added/, "remote-merge: left_added is present");
|
like ($report_r, qr/left_added/, "remote-merge: left_added is present");
|
||||||
like ($report_r, qr/right_added/, "remote-merge: right_added is present");
|
like ($report_r, qr/right_added/, "remote-merge: right_added is present");
|
||||||
like ($report_r, qr/H.*left_modified/, "remote-merge: left_modified ok");
|
like ($report_r, qr/H.*left_modified/, "remote-merge: left_modified ok");
|
||||||
like ($report_r, qr/\*.*left_newer.*stay/, "remote-merge: left_newer ok");
|
like ($report_r, qr/\*.*left_newer.*stay/, "remote-merge: left_newer ok");
|
||||||
like ($report_r, qr/realProject.*right_newer.*gym/, "remote-merge: right_newer ok");
|
like ($report_r, qr/realProject.*right_newer.*gym/, "remote-merge: right_newer ok");
|
||||||
|
|
||||||
$report_r = qx{../src/task rc:remote.rc export.csv};
|
$report_r = qx{../src/task rc:remote.rc export};
|
||||||
like ($report_r, qr/deleted.*left_deleted/, "remote-merge: left_deleted ok");
|
like ($report_r, qr/deleted.*left_deleted/, "remote-merge: left_deleted ok");
|
||||||
like ($report_r, qr/deleted.*right_deleted/, "remote-merge: right_deleted ok");
|
like ($report_r, qr/deleted.*right_deleted/, "remote-merge: right_deleted ok");
|
||||||
like ($report_r, qr/completed.*left_completed/, "remote-merge: left_completed ok");
|
like ($report_r, qr/completed.*left_completed/, "remote-merge: left_completed ok");
|
||||||
like ($report_r, qr/completed.*right_completed/, "remote-merge: right_completed ok");
|
like ($report_r, qr/completed.*right_completed/, "remote-merge: right_completed ok");
|
||||||
|
|
||||||
$report_r = qx(../src/task rc:remote.rc waiting);
|
$report_r = qx(../src/task rc:remote.rc waiting);
|
||||||
like ($report_r, qr/L.*right_modified/, "remote-merge: right_modified ok");
|
like ($report_r, qr/L.*right_modified/, "remote-merge: right_modified ok");
|
||||||
|
@ -228,49 +229,28 @@ if (open my $fh, 'remote/undo.data') {
|
||||||
ok ($good, "remote-merge: timestamps ok");
|
ok ($good, "remote-merge: timestamps ok");
|
||||||
|
|
||||||
# Cleanup.
|
# Cleanup.
|
||||||
unlink 'local/pending.data';
|
unlink qw(local/pending.data local/completed.data local/undo.data local/undo.save local/backlog.data local/synch.key local.rc);
|
||||||
ok (!-r 'local/pending.data', 'Removed local/pending.data');
|
ok (! -r 'local/pending.data' &&
|
||||||
|
! -r 'local/completed.data' &&
|
||||||
|
! -r 'local/undo.data' &&
|
||||||
|
! -r 'local/undo.save' &&
|
||||||
|
! -r 'local/backlog.data' &&
|
||||||
|
! -r 'local/synch_key.data' &&
|
||||||
|
! -r 'local.rc', 'Local Cleanup');
|
||||||
|
|
||||||
unlink 'local/completed.data';
|
unlink qw(remote/pending.data remote/completed.data remote/undo.data remote/backlog.data remote/synch.key remote.rc);
|
||||||
ok (!-r 'local/completed.data', 'Removed local/completed.data');
|
ok (! -r 'remote/pending.data' &&
|
||||||
|
! -r 'remote/completed.data' &&
|
||||||
|
! -r 'remote/undo.data' &&
|
||||||
|
! -r 'remote/backlog.data' &&
|
||||||
|
! -r 'remote/synch_key.data' &&
|
||||||
|
! -r 'remote.rc', 'Remove Cleanup');
|
||||||
|
|
||||||
unlink 'local/undo.data';
|
rmtree (['remote/extensions', 'remote', 'local/extensions', 'local'], 0, 1);
|
||||||
ok (!-r 'local/undo.data', 'Removed local/undo.data');
|
ok (! -e 'remote/extensions' &&
|
||||||
|
! -e 'remote' &&
|
||||||
unlink 'local/backlog.data';
|
! -e 'local/extensions' &&
|
||||||
ok (!-r 'local/backlog.data', 'Removed local/backlog.data');
|
! -e 'local', 'Removed dir local');
|
||||||
|
|
||||||
unlink 'local/synch.key';
|
|
||||||
ok (!-r 'local/synch.key', 'Removed local/synch.key');
|
|
||||||
|
|
||||||
unlink 'local/undo.save';
|
|
||||||
ok (!-r 'local/undo.save', 'Removed local/undo.save');
|
|
||||||
|
|
||||||
unlink 'local.rc';
|
|
||||||
ok (!-r 'local.rc', 'Removed local.rc');
|
|
||||||
|
|
||||||
unlink 'remote/pending.data';
|
|
||||||
ok (!-r 'remote/pending.data', 'Removed remote/pending.data');
|
|
||||||
|
|
||||||
unlink 'remote/completed.data';
|
|
||||||
ok (!-r 'remote/completed.data', 'Removed remote/completed.data');
|
|
||||||
|
|
||||||
unlink 'remote/undo.data';
|
|
||||||
ok (!-r 'remote/undo.data', 'Removed remote/undo.data');
|
|
||||||
|
|
||||||
unlink 'remote/backlog.data';
|
|
||||||
ok (!-r 'remote/backlog.data', 'Removed remote/backlog.data');
|
|
||||||
|
|
||||||
unlink 'remote/synch.key';
|
|
||||||
ok (!-r 'remote/synch.key', 'Removed remote/synch.key');
|
|
||||||
|
|
||||||
unlink 'remote.rc';
|
|
||||||
ok (!-r 'remote.rc', 'Removed remote.rc');
|
|
||||||
|
|
||||||
rmdir("remote");
|
|
||||||
ok (!-e "remote", "Removed dir remote");
|
|
||||||
rmdir("local");
|
|
||||||
ok (!-e "local", "Removed dir local");
|
|
||||||
|
|
||||||
exit 0;
|
exit 0;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue