mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-08-29 07:57:20 +02:00
Feature - merge command
- Merged patch that implements the first milestone of the merge feature. Thanks to Johannes Schlatow.
This commit is contained in:
parent
d7c446f010
commit
213a7a519b
12 changed files with 800 additions and 10 deletions
|
@ -154,6 +154,14 @@ Att& Att::operator= (const Att& other)
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
bool Att::operator== (const Att& other) const
|
||||||
|
{
|
||||||
|
return mName == other.mName &&
|
||||||
|
mMod == other.mMod &&
|
||||||
|
mValue == other.mValue;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
Att::~Att ()
|
Att::~Att ()
|
||||||
{
|
{
|
||||||
|
|
|
@ -41,6 +41,7 @@ public:
|
||||||
Att (const std::string&, int);
|
Att (const std::string&, int);
|
||||||
Att (const Att&);
|
Att (const Att&);
|
||||||
Att& operator= (const Att&);
|
Att& operator= (const Att&);
|
||||||
|
bool operator== (const Att&) const;
|
||||||
~Att ();
|
~Att ();
|
||||||
|
|
||||||
bool valid (const std::string&) const;
|
bool valid (const std::string&) const;
|
||||||
|
|
|
@ -112,6 +112,7 @@ void Cmd::load ()
|
||||||
commands.push_back ("_ids");
|
commands.push_back ("_ids");
|
||||||
commands.push_back ("_config");
|
commands.push_back ("_config");
|
||||||
commands.push_back ("_version");
|
commands.push_back ("_version");
|
||||||
|
commands.push_back ("_merge");
|
||||||
commands.push_back ("export.csv");
|
commands.push_back ("export.csv");
|
||||||
commands.push_back ("export.ical");
|
commands.push_back ("export.ical");
|
||||||
commands.push_back ("history.monthly");
|
commands.push_back ("history.monthly");
|
||||||
|
@ -233,7 +234,8 @@ bool Cmd::isReadOnlyCommand ()
|
||||||
// Commands that directly modify the data files.
|
// Commands that directly modify the data files.
|
||||||
bool Cmd::isWriteCommand ()
|
bool Cmd::isWriteCommand ()
|
||||||
{
|
{
|
||||||
if (command == context.stringtable.get (CMD_ADD, "add") ||
|
if (command == "_merge" ||
|
||||||
|
command == context.stringtable.get (CMD_ADD, "add") ||
|
||||||
command == context.stringtable.get (CMD_APPEND, "append") ||
|
command == context.stringtable.get (CMD_APPEND, "append") ||
|
||||||
command == context.stringtable.get (CMD_ANNOTATE, "annotate") ||
|
command == context.stringtable.get (CMD_ANNOTATE, "annotate") ||
|
||||||
command == context.stringtable.get (CMD_DENOTATE, "denotate") ||
|
command == context.stringtable.get (CMD_DENOTATE, "denotate") ||
|
||||||
|
|
|
@ -240,6 +240,7 @@ int Context::dispatch (std::string &out)
|
||||||
else if (cmd.command == "shell") { handleShell ( ); }
|
else if (cmd.command == "shell") { handleShell ( ); }
|
||||||
#endif
|
#endif
|
||||||
else if (cmd.command == "undo") { handleUndo ( ); }
|
else if (cmd.command == "undo") { handleUndo ( ); }
|
||||||
|
else if (cmd.command == "_merge") { handleMerge (out); }
|
||||||
else if (cmd.command == "_projects") { rc = handleCompletionProjects (out); }
|
else if (cmd.command == "_projects") { rc = handleCompletionProjects (out); }
|
||||||
else if (cmd.command == "_tags") { rc = handleCompletionTags (out); }
|
else if (cmd.command == "_tags") { rc = handleCompletionTags (out); }
|
||||||
else if (cmd.command == "_commands") { rc = handleCompletionCommands (out); }
|
else if (cmd.command == "_commands") { rc = handleCompletionCommands (out); }
|
||||||
|
|
|
@ -4,13 +4,14 @@ task_SOURCES = API.cpp Att.cpp Cmd.cpp Color.cpp Config.cpp Context.cpp \
|
||||||
Date.cpp Directory.cpp Duration.cpp File.cpp Filter.cpp \
|
Date.cpp Directory.cpp Duration.cpp File.cpp Filter.cpp \
|
||||||
Grid.cpp Hooks.cpp Keymap.cpp Location.cpp Nibbler.cpp \
|
Grid.cpp Hooks.cpp Keymap.cpp Location.cpp Nibbler.cpp \
|
||||||
Path.cpp Permission.cpp Record.cpp Sequence.cpp \
|
Path.cpp Permission.cpp Record.cpp Sequence.cpp \
|
||||||
StringTable.cpp Subst.cpp TDB.cpp Table.cpp Task.cpp Timer.cpp \
|
StringTable.cpp Subst.cpp TDB.cpp Table.cpp Task.cpp \
|
||||||
command.cpp custom.cpp edit.cpp export.cpp import.cpp \
|
Taskmod.cpp Timer.cpp command.cpp custom.cpp edit.cpp \
|
||||||
interactive.cpp main.cpp recur.cpp report.cpp rules.cpp rx.cpp \
|
export.cpp import.cpp interactive.cpp main.cpp recur.cpp \
|
||||||
text.cpp util.cpp API.h Att.h Cmd.h Color.h Config.h Context.h \
|
report.cpp rules.cpp rx.cpp text.cpp util.cpp API.h Att.h \
|
||||||
Date.h Directory.h Duration.h File.h Filter.h Grid.h Hooks.h \
|
Cmd.h Color.h Config.h Context.h Date.h Directory.h Duration.h \
|
||||||
Keymap.h Location.h Nibbler.h Path.h Permission.h Record.h \
|
File.h Filter.h Grid.h Hooks.h Keymap.h Location.h Nibbler.h \
|
||||||
Sequence.h StringTable.h Subst.h TDB.h Table.h Task.h Timer.h \
|
Path.h Permission.h Record.h Sequence.h StringTable.h Subst.h \
|
||||||
i18n.h main.h text.h util.h rx.h
|
TDB.h Table.h Task.h Taskmod.h Timer.h i18n.h main.h text.h \
|
||||||
|
util.h rx.h
|
||||||
task_CPPFLAGS=$(LUA_CFLAGS)
|
task_CPPFLAGS=$(LUA_CFLAGS)
|
||||||
task_LDFLAGS=$(LUA_LFLAGS)
|
task_LDFLAGS=$(LUA_LFLAGS)
|
||||||
|
|
496
src/TDB.cpp
496
src/TDB.cpp
|
@ -27,6 +27,8 @@
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <set>
|
||||||
|
#include <list>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
@ -42,8 +44,69 @@
|
||||||
#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 << std::endl; std::cout.flush()
|
||||||
|
#define DEBUG_STR_PART(str) std::cout << "DEBUG: " << str; std::cout.flush()
|
||||||
|
#define DEBUG_STR_END(str) std::cout << str << std::endl; 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 ("Failed to convert \"" + stream.str() + "\" to integer: " + tmod_tmp.getTimeStr());
|
||||||
|
}
|
||||||
|
|
||||||
|
// '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.
|
||||||
|
@ -783,6 +846,439 @@ void TDB::undo ()
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void TDB::merge (const std::string& mergeFile)
|
||||||
|
{
|
||||||
|
// list of modifications that we want to add to the local database
|
||||||
|
std::list<Taskmod> mods;
|
||||||
|
|
||||||
|
// 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 transactions to apply.");
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
rline = *rit;
|
||||||
|
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);
|
||||||
|
|
||||||
|
// found first matching lines?
|
||||||
|
if (lline.compare(rline) == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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 tasmods 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 << "Skipping the new local task " << (*lmod_it).getUuid() << std::endl;
|
||||||
|
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 the new remote task " << tmod.getUuid() << std::endl;
|
||||||
|
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");
|
||||||
|
|
||||||
|
DEBUG_STR("Merging modifications:");
|
||||||
|
|
||||||
|
// ------ 3. merge 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");
|
||||||
|
|
||||||
|
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 << "Applying remote changes for uuid " << uuid << std::endl;
|
||||||
|
|
||||||
|
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 << "Rejecting remote changes for uuid " << uuid << std::endl;
|
||||||
|
// inserting right mod into history of local database
|
||||||
|
// so that it can be restored later
|
||||||
|
|
||||||
|
// TODO feature: make rejected changes on the remote branch restorable
|
||||||
|
// Taskmod reverse_tmod;
|
||||||
|
//
|
||||||
|
// tmod_r.setBefore((*lmod_rit).getAfter());
|
||||||
|
// tmod_r.setTimestamp((*lmod_rit).getTimestamp()+1);
|
||||||
|
//
|
||||||
|
// reverse_tmod.setAfter(tmod_r.getBefore());
|
||||||
|
// reverse_tmod.setBefore(tmod_r.getAfter());
|
||||||
|
// reverse_tmod.setTimestamp(tmod_r.getTimestamp());
|
||||||
|
//
|
||||||
|
// mods.push_back(tmod_r);
|
||||||
|
// mods.push_back(reverse_tmod);
|
||||||
|
|
||||||
|
// 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.getBefore());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // for
|
||||||
|
|
||||||
|
DEBUG_STR("adding non-conflicting changes from the right branch");
|
||||||
|
mods.splice(mods.begin(), rmods);
|
||||||
|
|
||||||
|
DEBUG_STR("sorting taskmod list");
|
||||||
|
mods.sort();
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (rit == r.end())
|
||||||
|
{
|
||||||
|
// nothing happend on the remote branch
|
||||||
|
// local branch is up-to-date
|
||||||
|
throw std::string ("Database is already up-to-date.");
|
||||||
|
}
|
||||||
|
else // lit == undo.end()
|
||||||
|
{
|
||||||
|
// nothing happend on the local branch
|
||||||
|
std::cout << "No changes were made on the local database. Appending changes..." << std::endl;
|
||||||
|
|
||||||
|
// add remaining lines (remote branch) to the list of modifications
|
||||||
|
readTaskmods(r, rit, mods);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
// redo command
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
if (!mods.empty()) {
|
||||||
|
std::cout << "Running redo routine..." << std::endl;
|
||||||
|
|
||||||
|
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) != std::string::npos)
|
||||||
|
{
|
||||||
|
// Update the completed record.
|
||||||
|
std::cout << "Modifying " << uuid << std::endl;
|
||||||
|
|
||||||
|
// 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) != std::string::npos)
|
||||||
|
{
|
||||||
|
// Update the pending record.
|
||||||
|
std::cout << "Modifying " << uuid << std::endl;
|
||||||
|
|
||||||
|
// 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 " << uuid << std::endl;
|
||||||
|
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) != std::string::npos)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
std::cout << "Adding " << uuid << std::endl;
|
||||||
|
pending.push_back (tmod.getAfter().composeF4());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
std::cout << "Not adding duplicate " << uuid << std::endl;
|
||||||
|
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 + "'.";
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
mods.merge(lmods);
|
||||||
|
|
||||||
|
// generate undo.data format
|
||||||
|
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 + "'.";
|
||||||
|
}
|
||||||
|
else // nothing to be done
|
||||||
|
{
|
||||||
|
std::cout << "nothing to be done" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete objects
|
||||||
|
lmods.clear();
|
||||||
|
mods.clear();
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
FILE* TDB::openAndLock (const std::string& file)
|
FILE* TDB::openAndLock (const std::string& file)
|
||||||
{
|
{
|
||||||
|
|
|
@ -62,6 +62,7 @@ public:
|
||||||
int gc (); // Clean up pending
|
int gc (); // Clean up pending
|
||||||
int nextId ();
|
int nextId ();
|
||||||
void undo ();
|
void undo ();
|
||||||
|
void merge (const std::string&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FILE* openAndLock (const std::string&);
|
FILE* openAndLock (const std::string&);
|
||||||
|
|
183
src/Taskmod.cpp
Normal file
183
src/Taskmod.cpp
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// task - a command line task list manager.
|
||||||
|
//
|
||||||
|
// Copyright 2006 - 2010, Paul Beckingham.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify it under
|
||||||
|
// the terms of the GNU General Public License as published by the Free Software
|
||||||
|
// Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
// version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
// details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License along with
|
||||||
|
// this program; if not, write to the
|
||||||
|
//
|
||||||
|
// Free Software Foundation, Inc.,
|
||||||
|
// 51 Franklin Street, Fifth Floor,
|
||||||
|
// Boston, MA
|
||||||
|
// 02110-1301
|
||||||
|
// USA
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <assert.h>
|
||||||
|
#include "Taskmod.h"
|
||||||
|
|
||||||
|
Taskmod::Taskmod()
|
||||||
|
{
|
||||||
|
timestamp = 0;
|
||||||
|
bAfterSet = false;
|
||||||
|
bBeforeSet = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Taskmod::Taskmod(const Taskmod& other)
|
||||||
|
{
|
||||||
|
this->before = other.before;
|
||||||
|
this->after = other.after;
|
||||||
|
this->timestamp = other.timestamp;
|
||||||
|
this->bAfterSet = other.bAfterSet;
|
||||||
|
this->bBeforeSet = other.bBeforeSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
Taskmod::~Taskmod()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// OPERATORS
|
||||||
|
bool Taskmod::operator< (const Taskmod &compare)
|
||||||
|
{
|
||||||
|
return (timestamp < compare.getTimestamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Taskmod::operator> (const Taskmod &compare)
|
||||||
|
{
|
||||||
|
return (timestamp > compare.getTimestamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Taskmod::operator== (const Taskmod& compare)
|
||||||
|
{
|
||||||
|
return ( (compare.after == this->after)
|
||||||
|
&& (compare.before == this->before)
|
||||||
|
&& (compare.timestamp == this->timestamp) );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Taskmod::operator!= (const Taskmod& compare)
|
||||||
|
{
|
||||||
|
return !this->operator ==(compare);
|
||||||
|
}
|
||||||
|
|
||||||
|
Taskmod& Taskmod::operator= (const Taskmod& other)
|
||||||
|
{
|
||||||
|
if (this != &other) {
|
||||||
|
this->before = other.before;
|
||||||
|
this->after = other.after;
|
||||||
|
this->timestamp = other.timestamp;
|
||||||
|
this->bAfterSet = other.bAfterSet;
|
||||||
|
this->bBeforeSet = other.bBeforeSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HELPER
|
||||||
|
void Taskmod::reset(long timestamp)
|
||||||
|
{
|
||||||
|
this->bAfterSet = false;
|
||||||
|
this->bBeforeSet = false;
|
||||||
|
this->timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Taskmod::isNew()
|
||||||
|
{
|
||||||
|
return !bBeforeSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Taskmod::issetAfter()
|
||||||
|
{
|
||||||
|
return bAfterSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Taskmod::issetBefore()
|
||||||
|
{
|
||||||
|
return bBeforeSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Taskmod::isValid()
|
||||||
|
{
|
||||||
|
return (timestamp > 0) && (bAfterSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Taskmod::getUuid()
|
||||||
|
{
|
||||||
|
if (!bAfterSet) {
|
||||||
|
throw std::string("Taskmod::getUuid(): Task object not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
return after.get("uuid");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Taskmod::toString()
|
||||||
|
{
|
||||||
|
assert(bAfterSet);
|
||||||
|
|
||||||
|
std::stringstream stream;
|
||||||
|
stream << "time " << timestamp << "\n";
|
||||||
|
|
||||||
|
if (bBeforeSet) {
|
||||||
|
stream << "old " << before.composeF4();
|
||||||
|
}
|
||||||
|
|
||||||
|
stream << "new " << after.composeF4();
|
||||||
|
stream << "---\n";
|
||||||
|
|
||||||
|
return stream.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// SETTER
|
||||||
|
void Taskmod::setAfter(const Task& after)
|
||||||
|
{
|
||||||
|
this->after = after;
|
||||||
|
bAfterSet = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskmod::setBefore(const Task& before)
|
||||||
|
{
|
||||||
|
this->before = before;
|
||||||
|
bBeforeSet = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskmod::setTimestamp(long timestamp)
|
||||||
|
{
|
||||||
|
this->timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GETTER
|
||||||
|
Task& Taskmod::getAfter()
|
||||||
|
{
|
||||||
|
return after;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task& Taskmod::getBefore()
|
||||||
|
{
|
||||||
|
return before;
|
||||||
|
}
|
||||||
|
|
||||||
|
long Taskmod::getTimestamp() const
|
||||||
|
{
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Taskmod::getTimeStr() const
|
||||||
|
{
|
||||||
|
std::stringstream sstream;
|
||||||
|
sstream << timestamp;
|
||||||
|
return sstream.str();
|
||||||
|
}
|
||||||
|
|
77
src/Taskmod.h
Normal file
77
src/Taskmod.h
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// task - a command line task list manager.
|
||||||
|
//
|
||||||
|
// Copyright 2006 - 2010, Paul Beckingham.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify it under
|
||||||
|
// the terms of the GNU General Public License as published by the Free Software
|
||||||
|
// Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
// version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
// details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License along with
|
||||||
|
// this program; if not, write to the
|
||||||
|
//
|
||||||
|
// Free Software Foundation, Inc.,
|
||||||
|
// 51 Franklin Street, Fifth Floor,
|
||||||
|
// Boston, MA
|
||||||
|
// 02110-1301
|
||||||
|
// USA
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef INCLUDED_TASKMOD
|
||||||
|
#define INCLUDED_TASKMOD
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <Task.h>
|
||||||
|
|
||||||
|
class Taskmod {
|
||||||
|
|
||||||
|
public:
|
||||||
|
Taskmod();
|
||||||
|
Taskmod(const Taskmod& other);
|
||||||
|
~Taskmod();
|
||||||
|
|
||||||
|
// operators
|
||||||
|
bool operator< (const Taskmod& compare);
|
||||||
|
bool operator> (const Taskmod& compare);
|
||||||
|
bool operator== (const Taskmod& compare);
|
||||||
|
bool operator!= (const Taskmod& compare);
|
||||||
|
Taskmod& operator= (const Taskmod& other);
|
||||||
|
|
||||||
|
// helper
|
||||||
|
void reset(long timestamp=0);
|
||||||
|
bool isNew();
|
||||||
|
bool issetBefore();
|
||||||
|
bool issetAfter();
|
||||||
|
bool isValid();
|
||||||
|
|
||||||
|
std::string getUuid();
|
||||||
|
std::string toString();
|
||||||
|
|
||||||
|
// setter
|
||||||
|
void setAfter(const Task& after);
|
||||||
|
void setBefore(const Task& before);
|
||||||
|
void setTimestamp(long timestamp);
|
||||||
|
|
||||||
|
// getter
|
||||||
|
Task& getAfter();
|
||||||
|
Task& getBefore();
|
||||||
|
long getTimestamp() const;
|
||||||
|
std::string getTimeStr() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Task after;
|
||||||
|
Task before;
|
||||||
|
long timestamp;
|
||||||
|
bool bAfterSet;
|
||||||
|
bool bBeforeSet;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -516,6 +516,25 @@ void handleUndo ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void handleMerge (std::string& outs)
|
||||||
|
{
|
||||||
|
if (context.hooks.trigger ("pre-merge-command"))
|
||||||
|
{
|
||||||
|
std::string file = trim (context.task.get ("description"));
|
||||||
|
if (file.length () > 0)
|
||||||
|
{
|
||||||
|
context.tdb.lock (context.config.getBoolean ("locking"));
|
||||||
|
context.tdb.merge (file);
|
||||||
|
context.tdb.unlock ();
|
||||||
|
|
||||||
|
context.hooks.trigger ("post-merge-command");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw std::string ("You must specify a file to merge.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
int handleVersion (std::string &outs)
|
int handleVersion (std::string &outs)
|
||||||
{
|
{
|
||||||
|
|
|
@ -80,6 +80,7 @@ int handleAnnotate (std::string &);
|
||||||
int handleDenotate (std::string &);
|
int handleDenotate (std::string &);
|
||||||
int handleDuplicate (std::string &);
|
int handleDuplicate (std::string &);
|
||||||
void handleUndo ();
|
void handleUndo ();
|
||||||
|
void handleMerge (std::string&);
|
||||||
#ifdef FEATURE_SHELL
|
#ifdef FEATURE_SHELL
|
||||||
void handleShell ();
|
void handleShell ();
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -11,7 +11,7 @@ OBJECTS = ../t-TDB.o ../t-Task.o ../t-text.o ../t-Date.o ../t-Table.o \
|
||||||
../t-Grid.o ../t-Color.o ../t-rules.o ../t-recur.o ../t-custom.o \
|
../t-Grid.o ../t-Color.o ../t-rules.o ../t-recur.o ../t-custom.o \
|
||||||
../t-export.o ../t-import.o ../t-edit.o ../t-Timer.o \
|
../t-export.o ../t-import.o ../t-edit.o ../t-Timer.o \
|
||||||
../t-Permission.o ../t-Path.o ../t-File.o ../t-Directory.o \
|
../t-Permission.o ../t-Path.o ../t-File.o ../t-Directory.o \
|
||||||
../t-Hooks.o ../t-API.o ../t-rx.o
|
../t-Hooks.o ../t-API.o ../t-rx.o ../t-Taskmod.o
|
||||||
|
|
||||||
all: $(PROJECT)
|
all: $(PROJECT)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue