Index: vcpx/__init__.py
===================================================================
--- vcpx/__init__.py	(revision 1178)
+++ vcpx/__init__.py	(revision 1229)
@@ -19,2 +19,5 @@
 class TailorException(Exception):
     "Common base for tailor exceptions"
+
+class TailorBug(TailorException):
+    "Tailor bug (please report)"
Index: vcpx/changes.py
===================================================================
--- vcpx/changes.py	(revision 1225)
+++ vcpx/changes.py	(revision 1229)
@@ -11,4 +11,6 @@
 
 __docformat__ = 'reStructuredText'
+
+from vcpx import TailorBug
 
 class ChangesetEntry(object):
@@ -112,5 +114,5 @@
     def _set_date(self, date):
         if date and date.tzinfo is None:
-            raise "Tailor bug (please report): Changeset dates must have a timezone."
+            raise TailorBug("Changeset dates must have a timezone!")
         self.__date = date
 
Index: vcpx/source.py
===================================================================
--- vcpx/source.py	(revision 1180)
+++ vcpx/source.py	(revision 1229)
@@ -13,5 +13,5 @@
 __docformat__ = 'reStructuredText'
 
-from vcpx import TailorException
+from vcpx import TailorBug, TailorException
 from vcpx.workdir import WorkingDir
 
@@ -229,5 +229,5 @@
         """
 
-        raise "%s should override this method" % self.__class__
+        raise TailorBug("%s should override this method!" % self.__class__)
 
     def _applyChangeset(self, changeset):
@@ -240,5 +240,5 @@
         """
 
-        raise "%s should override this method" % self.__class__
+        raise TailorBug("%s should override this method!" % self.__class__)
 
     def checkoutUpstreamRevision(self, revision):
@@ -260,5 +260,5 @@
         """
 
-        raise "%s should override this method" % self.__class__
+        raise TailorBug("%s should override this method!" % self.__class__)
 
     def prepareSourceRepository(self):
Index: vcpx/target.py
===================================================================
--- vcpx/target.py	(revision 1209)
+++ vcpx/target.py	(revision 1229)
@@ -15,5 +15,5 @@
 import socket
 from signal import signal, SIGINT, SIG_IGN
-from vcpx import TailorException
+from vcpx import TailorBug, TailorException
 from vcpx.workdir import WorkingDir
 
@@ -342,5 +342,5 @@
         """
 
-        raise "%s should override this method" % self.__class__
+        raise TailorBug("%s should override this method!" % self.__class__)
 
     def _addSubtree(self, subdir):
@@ -393,5 +393,5 @@
         """
 
-        raise "%s should override this method" % self.__class__
+        raise TailorBug("%s should override this method!" % self.__class__)
 
     def _removeEntries(self, entries):
@@ -407,5 +407,5 @@
         """
 
-        raise "%s should override this method" % self.__class__
+        raise TailorBug("%s should override this method!" % self.__class__)
 
     def _editEntries(self, entries):
@@ -484,5 +484,5 @@
         """
 
-        raise "%s should override this method" % self.__class__
+        raise TailorBug("%s should override this method!" % self.__class__)
 
     def prepareWorkingDirectory(self, source_repo):
Index: vcpx/tests/__init__.py
===================================================================
--- vcpx/tests/__init__.py	(revision 1226)
+++ vcpx/tests/__init__.py	(revision 1230)
@@ -17,4 +17,5 @@
 from statefile import *
 from tailor import *
+from fixed_bugs import *
 
 class TailorTest(TestProgram):
Index: vcpx/repository/mock.py
===================================================================
--- vcpx/repository/mock.py	(revision 1230)
+++ vcpx/repository/mock.py	(revision 1230)
@@ -0,0 +1,134 @@
+# -*- mode: python; coding: utf-8 -*-
+# :Progetto: vcpx -- mock source backend
+# :Creato:   Sun Jul 16 02:50:04 CEST 2006
+# :Autore:   Adeodato Simó <dato@net.com.org.es>
+# :Licenza:  GNU General Public License
+#
+
+"""
+This module implements a mock source backend to be used in tests.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import os
+from shutil import rmtree
+from datetime import datetime, timedelta
+
+from vcpx.tzinfo import UTC
+from vcpx import TailorBug
+from vcpx.repository import Repository
+from vcpx.source import UpdatableSourceWorkingDir
+from vcpx.changes import Changeset, ChangesetEntry
+
+
+class MockRepository(Repository):
+    def create(self):
+        if not os.path.isdir(self.basedir):
+            os.makedirs(self.basedir)
+
+
+class MockWorkingDir(UpdatableSourceWorkingDir):
+    def __init__(self, *args, **kwargs):
+        super(MockWorkingDir, self).__init__(*args, **kwargs)
+        self.rev_offset = 0
+        self.changesets = []
+
+    def _getUpstreamChangesets(self, sincerev):
+        return self.changesets[sincerev-self.rev_offset:]
+
+    def _applyChangeset(self, changeset):
+        for e in changeset.entries:
+            e.apply(self.repository.basedir)
+
+        return []
+
+    def _checkoutUpstreamRevision(self, revision):
+        if revision == 'INITIAL':
+            cset = self.changesets[0]
+            self.rev_offset = cset.revision - 1
+            self._applyChangeset(cset)
+            return cset
+        else:
+            raise TailorBug("Don't know what to do!")
+
+    def _get_changesets(self):
+        if not self.__changesets:
+            raise TailorBug("Attempted to use empty MockWorkingDir!")
+        return self.__changesets
+
+    def _set_changesets(self, changesets):
+        self.__changesets = changesets
+
+    changesets = property(_get_changesets, _set_changesets)
+
+
+class MockChangeset(Changeset):
+    def __init__(self, log, entries):
+        super(MockChangeset, self).__init__(MockChangeset.Rev.next(),
+                MockChangeset.Date.next(), None, log, entries)
+
+    def Rev():
+        initial = 0
+
+        while True:
+            initial += 1
+            yield initial
+
+    def Date():
+        initial = datetime.now(UTC)
+        step = timedelta(seconds=1)
+
+        while True:
+            initial += step
+            yield initial
+
+    Rev  = Rev()
+    Date = Date()
+
+
+class MockChangesetEntry(ChangesetEntry):
+    def __init__(self, action, name, old_name=None, contents=None):
+        super(MockChangesetEntry, self).__init__(name)
+
+        self.isdir       = False
+        self.contents    = contents
+        self.old_name    = old_name
+        self.action_kind = action
+
+        if self.name[-1] == '/':
+            self.isdir = True
+            self.name  = self.name[:-1]
+
+    def apply(self, where):
+        name = os.path.join(where, self.name)
+        if self.action_kind == self.ADDED:
+            if self.isdir:
+                os.makedirs(name)
+            else:
+                dirname = os.path.dirname(name)
+                if not os.path.exists(dirname):
+                    os.makedirs(dirname)
+                f = file(name, 'w')
+                if self.contents is not None:
+                    f.write(self.contents)
+                f.close()
+        elif self.action_kind == self.DELETED:
+            if os.path.exists(name):
+                if self.isdir:
+                    rmtree(name)
+                else:
+                    os.unlink(name)
+        elif self.action_kind == self.RENAMED:
+            old_name = os.path.join(where, self.old_name)
+            if os.path.exists(oldname):
+                os.rename(old_name, name)
+        elif self.action_kind == self.UPDATED:
+            if self.contents is not None:
+                f = file(name, 'w')
+                f.write(self.contents)
+            else: # update timestamp
+                f = file(name, 'w+')
+            f.close()
+        else:
+            raise TailorBug("Unknown ChangesetEntry.action_kind: %s" % str(self.action_kind))
Index: vcpx/tests/fixed_bugs.py
===================================================================
--- vcpx/tests/fixed_bugs.py	(revision 1230)
+++ vcpx/tests/fixed_bugs.py	(revision 1230)
@@ -0,0 +1,98 @@
+# -*- mode: python; coding: utf-8 -*-
+# :Progetto: vcpx -- Prevents reintroduced bugs
+# :Creato:   Sun Jul 16 02:50:04 CEST 2006
+# :Autore:   Adeodato Simó <dato@net.com.org.es>
+# :Licenza:  GNU General Public License
+#
+
+from os.path import exists, join
+from unittest import TestCase
+from cStringIO import StringIO
+
+from vcpx.config import Config
+from vcpx.tailor import Tailorizer
+from vcpx.repository.mock import MockChangeset as Changeset, \
+                                 MockChangesetEntry as Entry
+
+
+class FixedBugs(TestCase):
+    """Ensure already fixed bugs don't get reintroduced"""
+
+    TESTDIR = '/tmp/tailor-tests/fixed-bugs'
+
+    ALL_TARGET_VCS = [ 'arx', 'bzr', 'cdv', 'cg', 'cvs', 'cvsps', 'darcs', 'git', 'hg', 'monotone', 'svn' ]
+
+    CONFIG = """\
+[%(test_name)s]
+# verbose = Yes
+source = mock:source
+target = %(vcs)s:target
+root-directory = %(test_dir)s
+state-file = state
+
+[mock:source]
+%(subdir)s.source
+
+[%(vcs)s:target]
+%(subdir)s
+module = /
+repository = file://%(test_dir)s/repo
+"""
+
+    def setUp(self):
+        from os import makedirs
+        from shutil import rmtree
+        from atexit import register
+
+        self.test_name = self.id().split('.')[-1]
+        self.test_dir  = join(self.TESTDIR, self.test_name)
+
+        if exists(self.test_dir):
+            rmtree(self.test_dir)
+        makedirs(self.test_dir)
+        register(rmtree, self.test_dir)
+
+        # defaults
+        self.TARGET_VCS = []
+        self.CHANGESETS = []
+        self.SHARED_BASEDIRS = False
+
+    def run_tailor(self):
+        test_name = self.test_name
+
+        for vcs in self.TARGET_VCS:
+            subdir   = self.SHARED_BASEDIRS and '#' or 'subdir = %s' % vcs
+            test_dir = join(self.test_dir, vcs)
+            config   = Config(StringIO(self.CONFIG % vars()), {})
+            project  = Tailorizer(test_name, config)
+            project.workingDir().source.changesets = self.CHANGESETS
+            project()
+
+    def testTicket64(self):
+        """#64: support add('foo/bar/baz') even if 'foo' was not previously added"""
+        self.TARGET_VCS = [ 'bzr', 'darcs', 'hg' ]
+        self.CHANGESETS = [
+            Changeset("Dummy first commit",
+                [ Entry(Entry.ADDED, 'dummy.txt'), ]),
+            Changeset("Add a/b/c",
+                [ Entry(Entry.ADDED, 'a/b/'),
+                  Entry(Entry.ADDED, 'a/b/c'),
+            ]),
+        ]
+        self.run_tailor()
+
+    def testTicket64_2(self):
+        """#64 (2): support update('foo2/bar') even if 'foo2' is added in the same changeset"""
+        self.TARGET_VCS = [ 'bzr', 'darcs', 'hg' ] # XXX bzr 0.8 fails :-?
+        self.CHANGESETS = [
+            Changeset("Dummy first commit",
+                [ Entry(Entry.ADDED, 'dummy.txt'), ]),
+            Changeset("Add a/b/c",
+                [ Entry(Entry.ADDED, 'a/b/c'),
+            ]),
+            Changeset("Add (cp) a2 and modify a2/b/c",
+                [ Entry(Entry.ADDED, 'a2/b/c'),
+                  Entry(Entry.UPDATED, 'a2/b/c', contents='foo')
+            ]),
+        ]
+        self.run_tailor()
