diff --git a/ChangeLog b/ChangeLog
index 4656eec5e..af78b2b89 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -12,6 +12,8 @@
is a set of IDs. This allows commands like "task delete 1 2 5-10,12".
+ Fixed bug in the ghistory report, which caused it to only show a new
month if a task was added during that month.
+ + New command "duplicate" which allows an existing task to be duplicated,
+ and also have modifications applied (thanks to David J Patrick).
------ old releases ------------------------------
diff --git a/checklist.txt b/checklist.txt
index 5cec663c1..7da5a57a2 100644
--- a/checklist.txt
+++ b/checklist.txt
@@ -1,3 +1,21 @@
+Adding New Command Checklist
+----------------------------
+
+- Create new handler in command.cpp or report.cpp.
+- Add prototype to task.h.
+- Add call to appropriate section in task.cpp.
+- Add usage info in task.cpp.
+- Add command name to list in parse.cpp.
+- Add unit tests in src/tests.
+- Add new configuration details to html/config.html.
+- Add command details to html/advanced.html.
+- Add description to ChangeLog, with attribution if the idea
+ came from a single user, but not if it came from multiple
+ users.
+- Add description to html/task.html.
+
+
+
Release Checklist
-----------------
diff --git a/html/advanced.html b/html/advanced.html
index b4abe30b0..356bb23cd 100644
--- a/html/advanced.html
+++ b/html/advanced.html
@@ -120,6 +120,13 @@ ID Project Pri Due Active Age Description
variable.
+
+ This duplicates task 1, then applies the modifications specified,
+ which change all "foo" to "bar" in the description and annotations,
+ adds the tag "tag", and sets the priority to "H".
+
+
There are two ways of getting rid of tasks - mark them as done, or
diff --git a/html/task.html b/html/task.html
index ac3e2038e..3bf21201d 100644
--- a/html/task.html
+++ b/html/task.html
@@ -123,6 +123,8 @@
is a set of IDs. This allows commands like "task delete 1 2 5-10,12".
New command "duplicate" which allows an existing task to be duplicated,
+ and also have modifications applied (thanks to David J Patrick).
diff --git a/src/command.cpp b/src/command.cpp
index fcc512fb9..de48bb40e 100644
--- a/src/command.cpp
+++ b/src/command.cpp
@@ -727,7 +727,7 @@ std::string handleAppend (TDB& tdb, T& task, Config& conf)
if (other->getId () == seq->getId () || // Self
(seq->getAttribute ("parent") != "" &&
seq->getAttribute ("parent") == other->getAttribute ("parent")) || // Sibling
- other->getUUID () == seq->getAttribute ("parent")) // Parent
+ other->getUUID () == seq->getAttribute ("parent")) // Parent
{
// A non-zero value forces a file write.
int changes = 0;
@@ -755,7 +755,62 @@ std::string handleAppend (TDB& tdb, T& task, Config& conf)
}
if (conf.get ("echo.command", true))
- out << "Modified " << count << " task" << (count == 1 ? "" : "s") << std::endl;
+ out << "Appended " << count << " task" << (count == 1 ? "" : "s") << std::endl;
+
+ return out.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string handleDuplicate (TDB& tdb, T& task, Config& conf)
+{
+ int count = 0;
+ std::stringstream out;
+ std::vector all;
+ tdb.allPendingT (all);
+
+ std::vector filtered = all;
+ filterSequence (filtered, task);
+ foreach (seq, filtered)
+ {
+ if (seq->getStatus () != T::recurring && seq->getAttribute ("parent") == "")
+ {
+ T dup (*seq);
+ dup.setUUID (uuid ()); // Needs a new UUID.
+
+ // Apply deltas.
+ deltaDescription (dup, task);
+ deltaTags (dup, task);
+ deltaAttributes (dup, task);
+ deltaSubstitutions (dup, task);
+
+ // A New task needs a new entry time.
+ char entryTime[16];
+ sprintf (entryTime, "%u", (unsigned int) time (NULL));
+ dup.setAttribute ("entry", entryTime);
+
+ if (!tdb.addT (dup))
+ throw std::string ("Could not create new task.");
+
+ if (conf.get ("echo.command", true))
+ out << "Duplicated "
+ << seq->getId ()
+ << " '"
+ << seq->getDescription ()
+ << "'"
+ << std::endl;
+ ++count;
+ }
+ else
+ out << "Task "
+ << seq->getId ()
+ << " '"
+ << seq->getDescription ()
+ << "' is a recurring task, and cannot be duplicated."
+ << std::endl;
+ }
+
+ if (conf.get ("echo.command", true))
+ out << "Duplicated " << count << " task" << (count == 1 ? "" : "s") << std::endl;
return out.str ();
}
diff --git a/src/parse.cpp b/src/parse.cpp
index e0f35039b..6bb7e224b 100644
--- a/src/parse.cpp
+++ b/src/parse.cpp
@@ -127,6 +127,7 @@ static const char* commands[] =
"completed",
"delete",
"done",
+ "duplicate",
"export",
"help",
"history",
diff --git a/src/task.cpp b/src/task.cpp
index bf725f8a5..49026df94 100644
--- a/src/task.cpp
+++ b/src/task.cpp
@@ -108,6 +108,10 @@ static std::string shortUsage (Config& conf)
table.addCell (row, 1, "task ID /from/to/g");
table.addCell (row, 2, "Performs all substitutions on the task description, for fixing mistakes");
+ row = table.addRow ();
+ table.addCell (row, 1, "task duplicate ID [tags] [attrs] [desc...]");
+ table.addCell (row, 2, "Duplicates the specified task, and allows modifications");
+
row = table.addRow ();
table.addCell (row, 1, "task delete ID");
table.addCell (row, 2, "Deletes the specified task");
@@ -861,17 +865,18 @@ std::string runTaskCommand (
else if (command == "help") { out = longUsage ( conf); }
// Commands that cause updates.
- else if (command == "" && task.getId ()) { cmdMod = true; out = handleModify (tdb, task, conf); }
- else if (command == "add") { cmdMod = true; out = handleAdd (tdb, task, conf); }
- else if (command == "append") { cmdMod = true; out = handleAppend (tdb, task, conf); }
- else if (command == "annotate") { cmdMod = true; out = handleAnnotate (tdb, task, conf); }
- else if (command == "done") { cmdMod = true; out = handleDone (tdb, task, conf); }
- else if (command == "undelete") { cmdMod = true; out = handleUndelete (tdb, task, conf); }
- else if (command == "delete") { cmdMod = true; out = handleDelete (tdb, task, conf); }
- else if (command == "start") { cmdMod = true; out = handleStart (tdb, task, conf); }
- else if (command == "stop") { cmdMod = true; out = handleStop (tdb, task, conf); }
- else if (command == "undo") { cmdMod = true; out = handleUndo (tdb, task, conf); }
- else if (command == "import") { cmdMod = true; out = handleImport (tdb, task, conf); }
+ else if (command == "" && task.getId ()) { cmdMod = true; out = handleModify (tdb, task, conf); }
+ else if (command == "add") { cmdMod = true; out = handleAdd (tdb, task, conf); }
+ else if (command == "append") { cmdMod = true; out = handleAppend (tdb, task, conf); }
+ else if (command == "annotate") { cmdMod = true; out = handleAnnotate (tdb, task, conf); }
+ else if (command == "done") { cmdMod = true; out = handleDone (tdb, task, conf); }
+ else if (command == "undelete") { cmdMod = true; out = handleUndelete (tdb, task, conf); }
+ else if (command == "delete") { cmdMod = true; out = handleDelete (tdb, task, conf); }
+ else if (command == "start") { cmdMod = true; out = handleStart (tdb, task, conf); }
+ else if (command == "stop") { cmdMod = true; out = handleStop (tdb, task, conf); }
+ else if (command == "undo") { cmdMod = true; out = handleUndo (tdb, task, conf); }
+ else if (command == "import") { cmdMod = true; out = handleImport (tdb, task, conf); }
+ else if (command == "duplicate") { cmdMod = true; out = handleDuplicate (tdb, task, conf); }
// Command that display IDs and therefore need TDB::gc first.
else if (command == "completed") { if (gc) gcMod = tdb.gc (); out = handleCompleted (tdb, task, conf); }
diff --git a/src/task.h b/src/task.h
index 2ecc4f755..bdb9e6090 100644
--- a/src/task.h
+++ b/src/task.h
@@ -89,6 +89,7 @@ std::string handleStop (TDB&, T&, Config&);
std::string handleUndo (TDB&, T&, Config&);
std::string handleColor (Config&);
std::string handleAnnotate (TDB&, T&, Config&);
+std::string handleDuplicate (TDB&, T&, Config&);
T findT (int, const std::vector &);
int deltaAppend (T&, T&);
int deltaDescription (T&, T&);
diff --git a/src/tests/duplicate.t b/src/tests/duplicate.t
new file mode 100755
index 000000000..d02321e86
--- /dev/null
+++ b/src/tests/duplicate.t
@@ -0,0 +1,66 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, 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
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 11;
+
+# Create the rc file.
+if (open my $fh, '>', 'dup.rc')
+{
+ print $fh "data.location=.\n";
+ close $fh;
+ ok (-r 'dup.rc', 'Created dup.rc');
+}
+
+# Test the duplicate command.
+qx{../task rc:dup.rc add foo};
+qx{../task rc:dup.rc duplicate 1};
+my $output = qx{../task rc:dup.rc info 2};
+like ($output, qr/ID\s+2/, 'duplicate new id');
+like ($output, qr/Status\s+Pending/, 'duplicate same status');
+like ($output, qr/Description\s+foo/, 'duplicate same description');
+
+# Test the en passant modification while duplicating.
+qx{../task rc:dup.rc duplicate 1 priority:H /foo/FOO/ +tag};
+$output = qx{../task rc:dup.rc info 3};
+like ($output, qr/ID\s+3/, 'duplicate new id');
+like ($output, qr/Status\s+Pending/, 'duplicate same status');
+like ($output, qr/Description\s+FOO/, 'duplicate modified description');
+like ($output, qr/Priority\s+H/, 'duplicate added priority');
+like ($output, qr/Tags\s+tag/, 'duplicate added tag');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'dup.rc';
+ok (!-r 'dup.rc', 'Removed dup.rc');
+
+exit 0;
+