diff --git a/product/ERP5Subversion/.cvsignore b/product/ERP5Subversion/.cvsignore
new file mode 100755
index 0000000000000000000000000000000000000000..d0743b7da685f380ff337cb0f7bf7f6fd7956ec3
--- /dev/null
+++ b/product/ERP5Subversion/.cvsignore
@@ -0,0 +1,2 @@
+.AppleDouble
+*.pyc
diff --git a/product/ERP5Subversion/Constraint/.cvsignore b/product/ERP5Subversion/Constraint/.cvsignore
new file mode 100755
index 0000000000000000000000000000000000000000..d0743b7da685f380ff337cb0f7bf7f6fd7956ec3
--- /dev/null
+++ b/product/ERP5Subversion/Constraint/.cvsignore
@@ -0,0 +1,2 @@
+.AppleDouble
+*.pyc
diff --git a/product/ERP5Subversion/Constraint/__init__.py b/product/ERP5Subversion/Constraint/__init__.py
new file mode 100755
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/product/ERP5Subversion/Document/.cvsignore b/product/ERP5Subversion/Document/.cvsignore
new file mode 100755
index 0000000000000000000000000000000000000000..d0743b7da685f380ff337cb0f7bf7f6fd7956ec3
--- /dev/null
+++ b/product/ERP5Subversion/Document/.cvsignore
@@ -0,0 +1,2 @@
+.AppleDouble
+*.pyc
diff --git a/product/ERP5Subversion/Document/__init__.py b/product/ERP5Subversion/Document/__init__.py
new file mode 100755
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/product/ERP5Subversion/Interface/.cvsignore b/product/ERP5Subversion/Interface/.cvsignore
new file mode 100755
index 0000000000000000000000000000000000000000..d0743b7da685f380ff337cb0f7bf7f6fd7956ec3
--- /dev/null
+++ b/product/ERP5Subversion/Interface/.cvsignore
@@ -0,0 +1,2 @@
+.AppleDouble
+*.pyc
diff --git a/product/ERP5Subversion/Interface/__init__.py b/product/ERP5Subversion/Interface/__init__.py
new file mode 100755
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/product/ERP5Subversion/Permissions.py b/product/ERP5Subversion/Permissions.py
new file mode 100755
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/product/ERP5Subversion/PropertySheet/.cvsignore b/product/ERP5Subversion/PropertySheet/.cvsignore
new file mode 100755
index 0000000000000000000000000000000000000000..d0743b7da685f380ff337cb0f7bf7f6fd7956ec3
--- /dev/null
+++ b/product/ERP5Subversion/PropertySheet/.cvsignore
@@ -0,0 +1,2 @@
+.AppleDouble
+*.pyc
diff --git a/product/ERP5Subversion/PropertySheet/__init__.py b/product/ERP5Subversion/PropertySheet/__init__.py
new file mode 100755
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/product/ERP5Subversion/SubversionClient.py b/product/ERP5Subversion/SubversionClient.py
new file mode 100755
index 0000000000000000000000000000000000000000..4b1d08555923d13eeb399a93868bcb9757237556
--- /dev/null
+++ b/product/ERP5Subversion/SubversionClient.py
@@ -0,0 +1,206 @@
+##############################################################################
+#
+# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Yoshinori Okuji <yo@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+##############################################################################
+
+from Acquisition import Implicit
+import time
+from Products.ERP5Type.Utils import convertToUpperCase
+from MethodObject import Method
+from Globals import InitializeClass
+from AccessControl import ClassSecurityInfo
+
+try:
+  import pysvn
+
+  class SubversionError(Exception):
+    """The base exception class for the Subversion interface.
+    """
+    pass
+    
+  class SubversionInstallationError(SubversionError):
+    """Raised when an installation is broken.
+    """
+    pass
+    
+  class SubversionTimeoutError(SubversionError):
+    """Raised when a Subversion transaction is too long.
+    """
+    pass
+  
+  class SubversionLoginError(SubversionError):
+    """Raised when an authentication is required.
+    """
+    def __init__(self, realm = None):
+      self._realm = realm
+  
+    def getRealm(self):
+      return self._realm
+      
+  class SubversionSSLTrustError(SubversionError):
+    """Raised when a SSL certificate is not trusted.
+    """
+    def __init__(self, trust_dict = None):
+      self._trust_dict = trust_dict
+  
+    def getTrustDict(self):
+      return self._trust_dict
+      
+  
+  class Callback:
+    """The base class for callback functions.
+    """
+    def __init__(self, client):
+      self.client = client
+  
+    def __call__(self, *args):
+      pass
+  
+  class CancelCallback(Callback):
+    def __call__(self):
+      current_time = time.time()
+      if current_time - self.client.creation_time > self.client.getTimeout():
+        raise SubversionTimeoutError, 'too long transaction'
+        #return True
+      return False
+  
+  class GetLogMessageCallback(Callback):
+    def __call__(self):
+      message = self.client.getLogMessage()
+      if message:
+        return True, message
+      return False, ''
+  
+  class GetLoginCallback(Callback):
+    def __call__(self, realm, username, may_save):
+      user, password = self.client.getLogin(realm)
+      if user is None:
+        raise SubversionLoginError(realm)
+        #return False, '', '', False
+      return True, user, password, False
+  
+  class NotifyCallback(Callback):
+    def __call__(self, event_dict):
+      # FIXME: should accumulate information for the user
+      pass
+  
+  class SSLServerTrustPromptCallback(Callback):
+    def __call__(self, trust_dict):
+      trust, permanent = self.client.trustSSLServer(trust_dict)
+      if not trust:
+        raise SubversionSSLTrustError(trust_dict)
+        #return False, 0, False
+      # XXX SSL server certificate failure bits are not defined in pysvn.
+      # 0x8 means that the CA is unknown.
+      return True, 0x8, permanent
+
+  # Wrap objects defined in pysvn so that skins have access to attributes in the ERP5 way.
+  class Getter(Method):
+    def __init__(self, key):
+      self._key = key
+  
+    def __call__(self, instance):
+      value = getattr(instance._obj, self._key)
+      if type(value) == type(u''):
+        value = value.encode('utf-8')
+      elif isinstance(value, pysvn.Entry):
+        value = Entry(value)
+      elif isinstance(value, pysvn.Revision):
+        value = Revision(value)
+      return value
+
+  def initializeAccessors(klass):
+    klass.security = ClassSecurityInfo()
+    for attr in klass.attribute_list:
+      name = 'get' + convertToUpperCase(attr)
+      setattr(klass, name, Getter(attr))
+      klass.security.declarePublic(name)
+    InitializeClass(Status)
+
+  class ObjectWrapper(Implicit):
+    attribute_list = ()
+    
+    def __init__(self, obj):
+      self._obj = obj
+  
+  class Status(ObjectWrapper):
+    attribute_list = ('path', 'entry', 'is_versioned', 'is_locked', 'is_copied', 'is_switched',
+                      'prop_status', 'text_status', 'repos_prop_status', 'repos_text_status')
+  initializeAccessors(Status)
+  
+  class Entry(ObjectWrapper):
+    attribute_list = ('checksum', 'commit_author', 'commit_revision', 'commit_time',
+                      'conflict_new', 'conflict_old', 'conflict_work', 'copy_from_revision',
+                      'copy_from_url', 'is_absent', 'is_copied', 'is_deleted', 'is_valid',
+                      'kind', 'name', 'properties_time', 'property_reject_file', 'repos',
+                      'revision', 'schedule', 'text_time', 'url', 'uuid')
+
+  class Revision(ObjectWrapper):
+    attribute_list = ('kind', 'date', 'number')
+  initializeAccessors(Revision)
+
+  
+  class SubversionClient(Implicit):
+    """This class wraps pysvn's Client class.
+    """
+    log_message = None
+    timeout = 60 * 5
+    
+    def __init__(self, **kw):
+      self.client = pysvn.Client()
+      self.client.set_auth_cache(0)
+      self.client.callback_cancel = CancelCallback(self)
+      self.client.callback_get_log_message = GetLogMessageCallback(self)
+      self.client.callback_get_login = GetLoginCallback(self)
+      self.client.callback_notify = NotifyCallback(self)
+      self.client.callback_ssl_server_trust_prompt = SSLServerTrustPromptCallback(self)
+      self.creation_time = time.time()
+      self.__dict__.update(kw)
+
+    def getLogMessage(self):
+      return self.log_message
+
+    def getTimeout(self):
+      return self.timeout
+
+    def getLogin(self, realm):
+      return self.aq_parent._getLogin(realm)
+
+    def trustSSLServer(self, trust_dict):
+      return self.aq_parent._trustSSLServer(trust_dict)
+
+    def status(self, path, **kw):
+      # Since plain Python classes are not convenient in Zope, convert the objects.
+      return [Status(x) for x in self.client.status(path, **kw)]
+
+  def newSubversionClient(container, **kw):
+    return SubversionClient(**kw).__of__(container)
+    
+except ImportError:
+  LOG('SubversionTool', WARNING,
+      'could not import pysvn; until pysvn is installed properly, this tool will not function.')
+  def newSubversionClient(container, **kw):
+    raise SubversionInstallationError, 'pysvn is not installed'
diff --git a/product/ERP5Subversion/Tool/.cvsignore b/product/ERP5Subversion/Tool/.cvsignore
new file mode 100755
index 0000000000000000000000000000000000000000..d0743b7da685f380ff337cb0f7bf7f6fd7956ec3
--- /dev/null
+++ b/product/ERP5Subversion/Tool/.cvsignore
@@ -0,0 +1,2 @@
+.AppleDouble
+*.pyc
diff --git a/product/ERP5Subversion/Tool/SubversionTool.py b/product/ERP5Subversion/Tool/SubversionTool.py
new file mode 100755
index 0000000000000000000000000000000000000000..1246906f4cdc1683ee2df6124c3835c9c399dcaa
--- /dev/null
+++ b/product/ERP5Subversion/Tool/SubversionTool.py
@@ -0,0 +1,236 @@
+##############################################################################
+#
+# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Yoshinori Okuji <yo@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+##############################################################################
+
+from Products.CMFCore.utils import UniqueObject
+from AccessControl import ClassSecurityInfo
+from Globals import InitializeClass, DTMLFile
+from Products.ERP5Type.Document.Folder import Folder
+from Products.ERP5Type import Permissions
+from Products.ERP5Subversion import _dtmldir
+from zLOG import LOG, WARNING, INFO
+from Products.ERP5Subversion.SubversionClient import newSubversionClient
+import os
+from DateTime import DateTime
+from base64 import b64encode, b64decode
+from cPickle import dumps, loads
+from App.config import getConfiguration
+from zExceptions import Unauthorized
+ 
+class SubversionTool(UniqueObject, Folder):
+  """The SubversionTool provides a Subversion interface to ERP5.
+  """
+  id = 'portal_subversion'
+  meta_type = 'ERP5 Subversion Tool'
+  portal_type = 'Subversion Tool'
+  allowed_types = ()
+
+  login_cookie_name = 'erp5_subversion_login'
+  ssl_trust_cookie_name = 'erp5_subversion_ssl_trust'
+  top_working_path = os.path.join(getConfiguration().instancehome, 'svn')
+  
+  # Declarative Security
+  security = ClassSecurityInfo()
+
+  #
+  #   ZMI methods
+  #
+  manage_options = ( ( { 'label'      : 'Overview'
+                        , 'action'     : 'manage_overview'
+                        }
+                      ,
+                      )
+                    + Folder.manage_options
+                    )
+
+  security.declareProtected( Permissions.ManagePortal, 'manage_overview' )
+  manage_overview = DTMLFile( 'explainSubversionTool', _dtmldir )
+
+  # Filter content (ZMI))
+  def __init__(self):
+      return Folder.__init__(self, SubversionTool.id)
+
+  # Filter content (ZMI))
+  def filtered_meta_types(self, user=None):
+      # Filters the list of available meta types.
+      all = SubversionTool.inheritedAttribute('filtered_meta_types')(self)
+      meta_types = []
+      for meta_type in self.all_meta_types():
+          if meta_type['name'] in self.allowed_types:
+              meta_types.append(meta_type)
+      return meta_types
+
+  def getTopWorkingPath(self):
+    return self.top_working_path
+
+  def _getWorkingPath(self, path):
+    if path[0] != '/':
+      path = os.path.join(self.top_working_path, path)
+    path = os.path.abspath(path)
+    if not path.startswith(self.top_working_path):
+      raise Unauthorized, 'unauthorized access to path %s' % path
+    return path
+    
+  def _encodeLogin(self, realm, user, password):
+    # Encode login information.
+    return b64encode(dumps((realm, user, password)))
+
+  def _decodeLogin(self, login):
+    # Decode login information.
+    return loads(b64decode(login))
+    
+  def setLogin(self, realm, user, password):
+    """Set login information.
+    """
+    # Get existing login information. Filter out old information.
+    login_list = []
+    request = self.REQUEST
+    cookie = request.get(self.login_cookie_name)
+    if cookie:
+      for login in cookie.split(','):
+        if self._decodeLogin(login)[0] != realm:
+          login_list.append(login)
+    # Set the cookie.
+    response = request.RESPONSE
+    login_list.append(self._encodeLogin(realm, user, password))
+    value = ','.join(login_list)
+    expires = (DateTime() + 1).toZone('GMT').rfc822()
+    response.setCookie(self.login_cookie_name, value, path = '/', expires = expires)
+
+  def _getLogin(self, target_realm):
+    request = self.REQUEST
+    cookie = request.get(self.login_cookie_name)
+    if cookie:
+      for login in cookie.split(','):
+        realm, user, password = self._decodeLogin(login)
+        if target_realm == realm:
+          return user, password
+    return None, None
+
+  def _encodeSSLTrust(self, trust_dict, permanent=False):
+    # Encode login information.
+    key_list = trust_dict.keys()
+    key_list.sort()
+    trust_item_list = tuple([(key, trust_dict[key]) for key in key_list])
+    return b64encode(dumps((trust_item_list, permanent)))
+
+  def _decodeSSLTrust(self, trust):
+    # Decode login information.
+    trust_item_list, permanent = loads(b64decode(login))
+    return dict(trust_item_list), permanent
+    
+  security.declareProtected(Permissions.ManagePortal, 'acceptSSLServer')
+  def acceptSSLServer(self, trust_dict, permanent=False):
+    """Accept a SSL server.
+    """
+    # Get existing trust information.
+    trust_list = []
+    request = self.REQUEST
+    cookie = request.get(self.ssl_trust_cookie_name)
+    if cookie:
+      trust.append(cookie)
+    # Set the cookie.
+    response = request.RESPONSE
+    trust_list.append(self._encodeSSLTrust(trust_dict, permanent))
+    value = ','.join(trust_list)
+    expires = (DateTime() + 1).toZone('GMT').rfc822()
+    response.setCookie(self.ssl_trust_cookie_name, value, path = '/', expires = expires)
+
+  def _trustSSLServer(self, target_trust_dict):
+    request = self.REQUEST
+    cookie = request.get(self.ssl_trust_cookie_name)
+    if cookie:
+      for trust in cookie.split(','):
+        trust_dict, permanent = self._decodeSSLTrust(trust)
+        for key in target_trust_dict.keys():
+          if target_trust_dict[key] != trust_dict.get(key):
+            continue
+        else:
+          return True, permanent
+    return False, False
+    
+  def _getClient(self, **kw):
+    # Get the svn client object.
+    return newSubversionClient(self, **kw)
+
+  security.declareProtected('Import/Export objects', 'update')
+  def update(self, path):
+    """Update a working copy.
+    """
+    client = self._getClient()
+    return client.update(self._getWorkingPath(path))
+
+  security.declareProtected('Import/Export objects', 'add')
+  def add(self, path):
+    """Add a file or a directory.
+    """
+    client = self._getClient()
+    return client.add(self._getWorkingPath(path))
+
+  security.declareProtected('Import/Export objects', 'remove')
+  def remove(self, path):
+    """Remove a file or a directory.
+    """
+    client = self._getClient()
+    return client.remove(self._getWorkingPath(path))
+
+  security.declareProtected('Import/Export objects', 'move')
+  def move(self, src, dest):
+    """Move/Rename a file or a directory.
+    """
+    client = self._getClient()
+    return client.move(src, dest)
+
+  security.declareProtected('Import/Export objects', 'diff')
+  def diff(self, path):
+    """Make a diff for a file or a directory.
+    """
+    client = self._getClient()
+    return client.diff(self._getWorkingPath(path))
+
+  security.declareProtected('Import/Export objects', 'revert')
+  def revert(self, path):
+    """Revert local changes in a file or a directory.
+    """
+    client = self._getClient()
+    return client.revert(self._getWorkingPath(path))
+
+  security.declareProtected('Import/Export objects', 'checkin')
+  def checkin(self, path, log_message = None):
+    """Commit local changes.
+    """
+    client = self._getClient(log_message = log_message)
+    return client.checkin(self._getWorkingPath(path))
+
+  security.declareProtected('Import/Export objects', 'status')
+  def status(self, path, **kw):
+    """Get status.
+    """
+    client = self._getClient()
+    return client.status(self._getWorkingPath(path), **kw)
+    
+InitializeClass(SubversionTool)
diff --git a/product/ERP5Subversion/Tool/__init__.py b/product/ERP5Subversion/Tool/__init__.py
new file mode 100755
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/product/ERP5Subversion/__init__.py b/product/ERP5Subversion/__init__.py
new file mode 100755
index 0000000000000000000000000000000000000000..ab28f9b0f62699c49c6c783d511d4bc3b0415d51
--- /dev/null
+++ b/product/ERP5Subversion/__init__.py
@@ -0,0 +1,55 @@
+##############################################################################
+#
+# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Yoshinori Okuji <yo@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+##############################################################################
+"""
+    ERP5 Free Software ERP
+"""
+
+# Update ERP5 Globals
+from Products.ERP5Type.Utils import initializeProduct, updateGlobals
+import sys, Permissions
+this_module = sys.modules[ __name__ ]
+document_classes = updateGlobals( this_module, globals(), permissions_module = Permissions)
+
+from Tool import SubversionTool
+
+# Define object classes and tools
+object_classes = ()
+portal_tools = (SubversionTool.SubversionTool,)
+content_classes = ()
+content_constructors = ()
+
+# Finish installation
+def initialize( context ):
+  import Document
+  initializeProduct(context, this_module, globals(),
+                         document_module = Document,
+                         document_classes = document_classes,
+                         object_classes = object_classes,
+                         portal_tools = portal_tools,
+                         content_constructors = content_constructors,
+                         content_classes = content_classes)
diff --git a/product/ERP5Subversion/dtml/explainSubversionTool.dtml b/product/ERP5Subversion/dtml/explainSubversionTool.dtml
new file mode 100755
index 0000000000000000000000000000000000000000..470a8e6d839878716cb64b61dcc0926945a0a188
--- /dev/null
+++ b/product/ERP5Subversion/dtml/explainSubversionTool.dtml
@@ -0,0 +1,6 @@
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<p>SubversionTool provides a Subversion interface for ERP5.</p>
+
+<dtml-var manage_page_footer>
diff --git a/product/ERP5Subversion/skins/.cvsignore b/product/ERP5Subversion/skins/.cvsignore
new file mode 100755
index 0000000000000000000000000000000000000000..d0743b7da685f380ff337cb0f7bf7f6fd7956ec3
--- /dev/null
+++ b/product/ERP5Subversion/skins/.cvsignore
@@ -0,0 +1,2 @@
+.AppleDouble
+*.pyc
diff --git a/product/ERP5Subversion/skins/__init__.py b/product/ERP5Subversion/skins/__init__.py
new file mode 100755
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/product/ERP5Subversion/tool.png b/product/ERP5Subversion/tool.png
new file mode 100755
index 0000000000000000000000000000000000000000..ab2c1f5a76622765a48e8f9cc516398b9a3a4213
Binary files /dev/null and b/product/ERP5Subversion/tool.png differ