Merge branch '1.9.3' of tasktools.org:task into 1.9.3

This commit is contained in:
Paul Beckingham 2010-10-03 18:49:52 -04:00
commit dea7b72b70
19 changed files with 364 additions and 23 deletions

6
NEWS
View file

@ -17,6 +17,7 @@ New Features in taskwarrior 1.9.3
detection of duplicate imports.
- New merge capability for syncing task data files.
- New push capability for distributing merged changes.
- New pull capability for copying data files from a remote location.
- When completing or modifying a task, the project status is displayed.
- The 'info' report is now colorized.
- Certain characters (#, $, @) are now supported for use in tags.
@ -36,6 +37,7 @@ New commands in taskwarrior 1.9.3
- New 'task merge <url>' command that can merge the local and an undo.data
file from another taskwarrior user, to sync across machines, for example.
- New 'task push <url>' command to distribute merged changes.
- New 'task pull <url>' command to copy data files from a remote location.
New configuration options in taskwarrior 1.9.3
@ -45,6 +47,10 @@ New configuration options in taskwarrior 1.9.3
variable rule.precedence.color. Try "task show rule.pre" to show the
default settings.
- merge.autopush to control whether pushing after merging is automated.
- merge.*.uri to configure source locations for the merge command
(e.g. merge.default.uri).
- push.*.uri to configure target locations for the push command.
- pull.*.uri to configure source locations for the pull command.
Newly deprecated features in taskwarrior 1.9.3

View file

@ -170,14 +170,14 @@ a second database.
Here is a basic example of the procedure:
$ rsync myremotehost:.task/undo.data /tmp/undo_remote.data
$ task merge /tmp/undo_remote.data
$ rsync ${HOME}/.task/*.data myremotehost:.task/
$ task merge ssh://user@myremotehost/.task/
$ task push ssh://user@myremotehost/.task/
First you need to get the undo.data file from the remote system, or removable
media. When the merge command completes, you should copy all the local .data
files to the remote system. This way you ensure that both systems are fully
synchronized.
The first command fetches the undo.data file from the remote system, reads the
changes made and updates the local database. When this merge command completes,
you should copy all the local .data files to the remote system either by using
the push command explicitly or by activating the merge.autopush feature in the
~/.taskrc file. This way you ensure that both systems are fully synchronized.
.TP
.B Q: The undo.data file gets very large - do I need it?

View file

@ -143,9 +143,33 @@ Exports all tasks in YAML 1.1 format.
Redirect the output to a file, if you wish to save it, or pipe it to another command.
.TP
.B merge path/to/second/undo.data
.B merge URL
Merges two task databases by comparing the modifications that are stored in the
undo.data files. The location of the second undo.data file must be passed on as argument.
undo.data files. The location of the second undo.data file must be passed on as argument. URL may have the following syntaxes:
ssh://[user@]host.xz[:port]/path/to/undo.data
rsync://[user@]host.xz[:port]/path/to/undo.data
[user@]host.xz:path/to/undo.data
/path/to/local/undo.data
You can set aliases for frequently used URLs in the .taskrc.
.TP
.B push URL
Pushes the task database to a remote another location for distributing the
changes made by the merge command.
(See annotations above for valid URL syntaxes.)
.TP
.B pull URL
Overwrites the task database with those files found at the URL.
(See annotations above for valid URL syntaxes.)
.TP
.B color [sample | legend]

View file

@ -120,7 +120,21 @@ _task()
*)
case "${prev}" in
merge)
COMPREPLY=( $(compgen -o "default" -- ${cur}) )
local servers=$(_task_get_config | grep merge | grep uri | sed 's/^merge\.\(.*\)\.uri/\1/')
COMPREPLY=( $(compgen -W "${servers}" -- ${cur}) )
_known_hosts_real -a "$cur"
return 0
;;
push)
local servers=$(_task_get_config | grep push | grep uri | sed 's/^push\.\(.*\)\.uri/\1/')
COMPREPLY=( $(compgen -W "${servers}" -- ${cur}) )
_known_hosts_real -a "$cur"
return 0
;;
pull)
local servers=$(_task_get_config | grep pull | grep uri | sed 's/^pull\.\(.*\)\.uri/\1/')
COMPREPLY=( $(compgen -W "${servers}" -- ${cur}) )
_known_hosts_real -a "$cur"
return 0
;;
esac

View file

@ -173,7 +173,8 @@ void Cmd::load ()
commands.push_back (context.stringtable.get (CMD_UNDO, "undo"));
commands.push_back (context.stringtable.get (CMD_VERSION, "version"));
commands.push_back (context.stringtable.get (CMD_MERGE, "merge"));
commands.push_back (context.stringtable.get (CMD_PUSH, "push"));
commands.push_back (context.stringtable.get (CMD_PUSH, "push"));
commands.push_back (context.stringtable.get (CMD_PULL, "pull"));
// Now load the custom reports.
std::vector <std::string> all;
@ -277,6 +278,7 @@ bool Cmd::isWriteCommand ()
command == context.stringtable.get (CMD_IMPORT, "import") ||
command == context.stringtable.get (CMD_LOG, "log") ||
command == context.stringtable.get (CMD_PREPEND, "prepend") ||
command == context.stringtable.get (CMD_PULL, "pull") ||
command == context.stringtable.get (CMD_START, "start") ||
command == context.stringtable.get (CMD_STOP, "stop") ||
command == context.stringtable.get (CMD_UNDO, "undo"))

View file

@ -80,7 +80,6 @@ std::string Config::defaults =
"recurrence.indicator=R # What to show as a task recurrence indicator\n"
"recurrence.limit=1 # Number of future recurring pending tasks\n"
"undo.style=side # Undo style - can be 'side', or 'diff'\n"
"merge.autopush=ask # Push database to remote origin after merge: yes, no, ask\n"
"\n"
"# Dates\n"
"dateformat=m/d/Y # Preferred input and display date format\n"
@ -232,6 +231,11 @@ std::string Config::defaults =
"fontunderline=yes # Uses underlines rather than -------\n"
"shell.prompt=task> # Prompt used by the shell command\n"
"\n"
"# Merge options\n"
"merge.autopush=ask # Push database to remote origin after merge: yes, no, ask\n"
"#merge.default.uri=user@host.xz:.task/\n"
"#pull.default.uri=rsync://host.xz/task-backup/\n"
"\n"
"# Import heuristics - alternate names for fields (comma-separated list of names)\n"
"#import.synonym.bg=?\n"
"#import.synonym.description=?\n"

View file

@ -244,6 +244,7 @@ int Context::dispatch (std::string &out)
else if (cmd.command == "merge") { tdb.gc ();
handleMerge (out); }
else if (cmd.command == "push") { handlePush (out); }
else if (cmd.command == "pull") { handlePull (out); }
else if (cmd.command == "_projects") { rc = handleCompletionProjects (out); }
else if (cmd.command == "_tags") { rc = handleCompletionTags (out); }
else if (cmd.command == "_commands") { rc = handleCompletionCommands (out); }

View file

@ -167,6 +167,8 @@ Hooks::Hooks ()
validProgramEvents.push_back ("post-prepend-command");
validProgramEvents.push_back ("pre-projects-command");
validProgramEvents.push_back ("post-projects-command");
validProgramEvents.push_back ("pre-pull-command");
validProgramEvents.push_back ("post-pull-command");
validProgramEvents.push_back ("pre-push-command");
validProgramEvents.push_back ("post-push-command");
validProgramEvents.push_back ("pre-shell-command");

View file

@ -11,7 +11,8 @@ task_SOURCES = API.cpp API.h Att.cpp Att.h Cmd.cpp Cmd.h Color.cpp Color.h \
StringTable.h Subst.cpp Subst.h TDB.cpp TDB.h Table.cpp Table.h \
Task.cpp Task.h Taskmod.cpp Taskmod.h Thread.cpp Thread.h \
Timer.cpp Timer.h Transport.cpp Transport.h TransportSSH.cpp \
TransportSSH.h Tree.cpp Tree.h command.cpp custom.cpp \
TransportSSH.h TransportRSYNC.cpp TransportRSYNC.h \
Tree.cpp Tree.h command.cpp custom.cpp \
dependency.cpp edit.cpp export.cpp i18n.h import.cpp \
interactive.cpp main.cpp main.h recur.cpp report.cpp rules.cpp \
rx.cpp rx.h text.cpp text.h util.cpp util.h

View file

@ -31,6 +31,7 @@
#include <sys/wait.h>
#include "Transport.h"
#include "TransportSSH.h"
#include "TransportRSYNC.h"
////////////////////////////////////////////////////////////////////////////////
Transport::Transport (const std::string& host, const std::string& path, const std::string& user="", const std::string& port="")
@ -60,6 +61,7 @@ void Transport::parseUri(std::string uri)
{
std::string::size_type pos;
std::string uripart;
std::string pathDelimiter = "/";
user = "";
port = "";
@ -67,11 +69,20 @@ void Transport::parseUri(std::string uri)
// skip ^.*://
if ((pos = uri.find ("://")) != std::string::npos)
{
protocol = uri.substr(0, pos);
uri = uri.substr (pos+3);
// standard syntax: protocol://[user@]host.xz[:port]/path/to/undo.data
pathDelimiter = "/";
}
else
{
protocol = "ssh";
// scp-like syntax: [user@]host.xz:path/to/undo.data
pathDelimiter = ":";
}
// get host part
if ((pos = uri.find ("/")) != std::string::npos)
if ((pos = uri.find (pathDelimiter)) != std::string::npos)
{
host = uri.substr (0, pos);
path = uri.substr (pos+1);
@ -88,6 +99,8 @@ void Transport::parseUri(std::string uri)
host = host.substr (pos+1);
}
// remark: this find() will never be != npos for scp-like syntax
// because we found pathDelimiter, which is ":", before
if ((pos = host.find (":")) != std::string::npos)
{
port = host.substr (pos+1);
@ -99,7 +112,15 @@ void Transport::parseUri(std::string uri)
Transport* Transport::getTransport(const std::string& uri)
{
if (uri.find("ssh://") == 0) {
return new TransportSSH(uri);
return new TransportSSH(uri);
}
else if (uri.find("rsync://") == 0) {
return new TransportRSYNC(uri);
}
else if ( (uri.find(":") != std::string::npos)
&& (uri.find("://") == std::string::npos) )
{
return new TransportSSH(uri);
}
return NULL;

View file

@ -44,6 +44,7 @@ public:
protected:
std::string executable;
std::string protocol;
std::vector<std::string> arguments;
std::string host;

130
src/TransportRSYNC.cpp Normal file
View file

@ -0,0 +1,130 @@
////////////////////////////////////////////////////////////////////////////////
// taskwarrior - a command line task list manager.
//
// Copyright 2010, Johannes Schlatow.
// 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 "TransportRSYNC.h"
////////////////////////////////////////////////////////////////////////////////
TransportRSYNC::TransportRSYNC(const std::string& uri) : Transport(uri)
{
executable = "rsync";
}
////////////////////////////////////////////////////////////////////////////////
TransportRSYNC::TransportRSYNC(
const std::string& host,
const std::string& path,
const std::string& user,
const std::string& port) : Transport (host,path,user,port)
{
executable = "rsync";
}
////////////////////////////////////////////////////////////////////////////////
void TransportRSYNC::send(const std::string& source)
{
if (host == "") {
throw std::string ("Hostname is empty");
}
// Is there more than one file to transfer?
// Then path has to end with a '/'
if ( (source.find ("*") != std::string::npos)
|| (source.find ("?") != std::string::npos)
|| (source.find (" ") != std::string::npos) )
{
std::string::size_type pos;
pos = path.find_last_of ("/");
if (pos != path.length()-1)
{
path = path.substr (0, pos+1);
}
}
// cmd line is: rsync [--port=PORT] source [user@]host::path
if (port != "")
{
arguments.push_back ("--port=" + port);
}
arguments.push_back (source);
if (user != "")
{
arguments.push_back (user + "@" + host + "::" + path);
}
else
{
arguments.push_back (host + "::" + path);
}
if (execute())
throw std::string ("Failed to run rsync!");
}
////////////////////////////////////////////////////////////////////////////////
void TransportRSYNC::recv(std::string target)
{
if (host == "") {
throw std::string ("Hostname is empty");
}
// Is there more than one file to transfer?
// Then target has to end with a '/'
if ( (path.find ("*") != std::string::npos)
|| (path.find ("?") != std::string::npos) )
{
std::string::size_type pos;
pos = target.find_last_of ("/");
if (pos != target.length()-1)
{
target = target.substr( 0, pos+1);
}
}
// cmd line is: rsync [--port=PORT] [user@]host::path target
if (port != "")
{
arguments.push_back ("--port=" + port);
}
if (user != "")
{
arguments.push_back (user + "@" + host + "::" + path);
}
else
{
arguments.push_back (host + "::" + path);
}
arguments.push_back (target);
if (execute())
throw std::string ("Failed to run rsync!");
}
////////////////////////////////////////////////////////////////////////////////

44
src/TransportRSYNC.h Normal file
View file

@ -0,0 +1,44 @@
////////////////////////////////////////////////////////////////////////////////
// taskwarrior - a command line task list manager.
//
// Copyright 2010, Johannes Schlatow.
// 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_TRANSPORTRSYNC
#define INCLUDED_TRANSPORTRSYNC
#include <string>
#include <Transport.h>
class TransportRSYNC : public Transport {
public:
TransportRSYNC (const std::string&);
TransportRSYNC (const std::string&, const std::string&, const std::string&, const std::string&);
virtual void send (const std::string&);
virtual void recv (std::string);
};
#endif

View file

@ -599,6 +599,20 @@ void handleMerge (std::string& outs)
std::string file = trim (context.task.get ("description"));
std::string tmpfile = "";
if (file.length () == 0)
{
// get default target from config
file = context.config.get ("merge.default.uri");
}
else
{
// replace argument with uri from config
std::string tmp = context.config.get ("merge." + file + ".uri");
if (tmp != "")
file = tmp;
}
if (file.length () > 0)
{
Directory location (context.config.get ("data.location"));
@ -655,6 +669,20 @@ void handlePush (std::string& outs)
{
std::string file = trim (context.task.get ("description"));
if (file.length () == 0)
{
// get default target from config
file = context.config.get ("push.default.uri");
}
else
{
// replace argument with uri from config
std::string tmp = context.config.get ("push." + file + ".uri");
if (tmp != "")
file = tmp;
}
if (file.length () > 0)
{
Directory location (context.config.get ("data.location"));
@ -677,6 +705,58 @@ void handlePush (std::string& outs)
}
}
////////////////////////////////////////////////////////////////////////////////
void handlePull (std::string& outs)
{
if (context.hooks.trigger ("pre-pull-command"))
{
std::string file = trim (context.task.get ("description"));
if (file.length () == 0)
{
// get default target from config
file = context.config.get ("pull.default.uri");
}
else
{
// replace argument with uri from config
std::string tmp = context.config.get ("pull." + file + ".uri");
if (tmp != "")
file = tmp;
}
if (file.length () > 0)
{
Directory location (context.config.get ("data.location"));
// add *.data to path if necessary
if (file.find ("*.data") == std::string::npos)
{
if (file[file.length()-1] != '/')
file += "/";
file += "*.data";
}
Transport* transport;
if ((transport = Transport::getTransport (file)) != NULL )
{
transport->recv (location.data + "/");
delete transport;
}
else
{
throw std::string ("Pull failed");
}
context.hooks.trigger ("post-pull-command");
}
else // TODO : get default target from config file
throw std::string ("You must specify a target.");
}
}
////////////////////////////////////////////////////////////////////////////////
int handleVersion (std::string &outs)
{

View file

@ -100,6 +100,7 @@
#define CMD_SHOW 231
#define CMD_MERGE 232
#define CMD_PUSH 233
#define CMD_PULL 234
// 3xx Attributes
#define ATT_PROJECT 300

View file

@ -79,6 +79,7 @@ int handleDuplicate (std::string &);
void handleUndo ();
void handleMerge (std::string&);
void handlePush (std::string&);
void handlePull (std::string&);
#ifdef FEATURE_SHELL
void handleShell ();
#endif

View file

@ -209,7 +209,11 @@ int shortUsage (std::string &outs)
row = table.addRow ();
table.addCell (row, 1, "task push URL");
table.addCell (row, 2, "Pushes the local undo.data files to the URL.");
table.addCell (row, 2, "Pushes the local *.data files to the URL.");
row = table.addRow ();
table.addCell (row, 1, "task pull URL");
table.addCell (row, 2, "Overwrites the local *.data files with those found at the URL.");
row = table.addRow ();
table.addCell (row, 1, "task export.ical");

View file

@ -14,7 +14,7 @@ OBJECTS = ../t-TDB.o ../t-Task.o ../t-text.o ../t-Date.o ../t-Table.o \
../t-Permission.o ../t-Path.o ../t-File.o ../t-Directory.o \
../t-Hooks.o ../t-API.o ../t-rx.o ../t-Taskmod.o ../t-dependency.o \
../t-Transport.o ../t-TransportSSH.o ../t-Sensor.o ../t-Thread.o \
../t-Lisp.o ../t-Rectangle.o ../t-Tree.o
../t-Lisp.o ../t-Rectangle.o ../t-Tree.o ../t-TransportRSYNC.o
all: $(PROJECT)

View file

@ -42,6 +42,7 @@ class TransportTest : public Transport
std::string getPath() { return path; };
std::string getUser() { return user; };
std::string getPort() { return port; };
std::string getProt() { return protocol; };
virtual void recv(std::string) {};
virtual void send(const std::string&) {};
@ -50,31 +51,35 @@ class TransportTest : public Transport
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
UnitTest t (16);
UnitTest t (20);
TransportTest tport1 ("asfd://user@host/folder/");
t.is (tport1.getUser (), "user", "Transport::parseUri() : asfd://user@host/folder/");
t.is (tport1.getHost (), "host", "Transport::parseUri() : asfd://user@host/folder/");
t.is (tport1.getPort (), "", "Transport::parseUri() : asfd://user@host/folder/");
t.is (tport1.getPath (), "folder/", "Transport::parseUri() : asfd://user@host/folder/");
t.is (tport1.getProt (), "asfd", "Transport::parseUri() : asfd://user@host/folder/");
TransportTest tport2 ("user@host:22/folder/file.test");
TransportTest tport2 ("user@host:folder/file.test");
t.is (tport2.getUser (), "user", "Transport::parseUri() : user@host:22/folder/file.test");
t.is (tport2.getHost (), "host", "Transport::parseUri() : user@host:22/folder/file.test");
t.is (tport2.getPort (), "22", "Transport::parseUri() : user@host:22/folder/file.test");
t.is (tport2.getPort (), "", "Transport::parseUri() : user@host:22/folder/file.test");
t.is (tport2.getPath (), "folder/file.test", "Transport::parseUri() : user@host:22/folder/file.test");
t.is (tport2.getProt (), "ssh", "Transport::parseUri() : user@host:22/folder/file.test");
TransportTest tport3 ("hostname.abc.de/file.test");
TransportTest tport3 ("rsync://hostname.abc.de:1234/file.test");
t.is (tport3.getUser (), "", "Transport::parseUri() : hostname.abc.de/file.test");
t.is (tport3.getHost (), "hostname.abc.de", "Transport::parseUri() : hostname.abc.de/file.test");
t.is (tport3.getPort (), "", "Transport::parseUri() : hostname.abc.de/file.test");
t.is (tport3.getPort (), "1234", "Transport::parseUri() : hostname.abc.de/file.test");
t.is (tport3.getPath (), "file.test", "Transport::parseUri() : hostname.abc.de/file.test");
t.is (tport3.getProt (), "rsync", "Transport::parseUri() : hostname.abc.de/file.test");
TransportTest tport4 ("hostname/");
TransportTest tport4 ("hostname:");
t.is (tport4.getUser (), "", "Transport::parseUri() : hostname/");
t.is (tport4.getHost (), "hostname", "Transport::parseUri() : hostname/");
t.is (tport4.getPort (), "", "Transport::parseUri() : hostname/");
t.is (tport4.getPath (), "", "Transport::parseUri() : hostname/");
t.is (tport4.getProt (), "ssh", "Transport::parseUri() : hostname/");
return 0;
}