eric6/Plugins/VcsPlugins/vcsPySvn/subversion.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7167
b3557e77314a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/Plugins/VcsPlugins/vcsPySvn/subversion.py	Sun Apr 14 15:09:21 2019 +0200
@@ -0,0 +1,2618 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2006 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the version control systems interface to Subversion.
+"""
+
+from __future__ import unicode_literals
+
+import os
+import sys
+import shutil
+import time
+
+from PyQt5.QtCore import Qt, QMutexLocker, pyqtSignal, QRegExp, QDateTime, \
+    QCoreApplication
+from PyQt5.QtWidgets import QLineEdit, QDialog, QInputDialog, QApplication
+
+from E5Gui.E5Application import e5App
+from E5Gui import E5MessageBox
+
+from VCS.VersionControl import VersionControl
+
+import pysvn
+
+from .SvnDialog import SvnDialog
+from .SvnUtilities import getConfigPath, amendConfig, createDefaultConfig
+
+import Utilities
+
+
+class Subversion(VersionControl):
+    """
+    Class implementing the version control systems interface to Subversion.
+    
+    @signal committed() emitted after the commit action has completed
+    """
+    committed = pyqtSignal()
+    
+    def __init__(self, plugin, parent=None, name=None):
+        """
+        Constructor
+        
+        @param plugin reference to the plugin object
+        @param parent parent widget (QWidget)
+        @param name name of this object (string)
+        """
+        VersionControl.__init__(self, parent, name)
+        self.defaultOptions = {
+            'global': [''],
+            'commit': [''],
+            'checkout': [''],
+            'update': [''],
+            'add': [''],
+            'remove': [''],
+            'diff': [''],
+            'log': [''],
+            'history': [''],
+            'status': [''],
+            'tag': [''],
+            'export': ['']
+        }
+        self.interestingDataKeys = [
+            "standardLayout",
+        ]
+        
+        self.__plugin = plugin
+        self.__ui = parent
+        
+        self.options = self.defaultOptions
+        self.otherData["standardLayout"] = True
+        self.tagsList = []
+        self.branchesList = []
+        self.allTagsBranchesList = []
+        self.mergeList = [[], [], []]
+        self.showedTags = False
+        self.showedBranches = False
+        
+        self.tagTypeList = [
+            'tags',
+            'branches'
+        ]
+        
+        self.commandHistory = []
+        self.wdHistory = []
+        
+        if pysvn.version >= (1, 4, 3, 0) and \
+                "SVN_ASP_DOT_NET_HACK" in os.environ:
+            self.adminDir = '_svn'
+        else:
+            self.adminDir = '.svn'
+        
+        self.log = None
+        self.diff = None
+        self.sbsDiff = None
+        self.status = None
+        self.propList = None
+        self.tagbranchList = None
+        self.blame = None
+        self.repoBrowser = None
+        self.logBrowser = None
+        
+        self.statusCache = {}
+        
+        self.__commitData = {}
+        self.__commitDialog = None
+        
+        self.__wcng = True
+        # assume new generation working copy metadata format
+    
+    def getPlugin(self):
+        """
+        Public method to get a reference to the plugin object.
+        
+        @return reference to the plugin object (VcsPySvnPlugin)
+        """
+        return self.__plugin
+    
+    def getClient(self):
+        """
+        Public method to create and initialize the pysvn client object.
+        
+        @return the pysvn client object (pysvn.Client)
+        """
+        configDir = ""
+        authCache = True
+        for arg in self.options['global']:
+            if arg.startswith("--config-dir"):
+                configDir = arg.split("=", 1)[1]
+            if arg.startswith("--no-auth-cache"):
+                authCache = False
+        
+        client = pysvn.Client(configDir)
+        client.exception_style = 1
+        client.set_auth_cache(authCache)
+        
+        return client
+    
+    ###########################################################################
+    ## Methods of the VCS interface
+    ###########################################################################
+    
+    def vcsShutdown(self):
+        """
+        Public method used to shutdown the Subversion interface.
+        """
+        if self.log is not None:
+            self.log.close()
+        if self.diff is not None:
+            self.diff.close()
+        if self.sbsDiff is not None:
+            self.sbsDiff.close()
+        if self.status is not None:
+            self.status.close()
+        if self.propList is not None:
+            self.propList.close()
+        if self.tagbranchList is not None:
+            self.tagbranchList.close()
+        if self.blame is not None:
+            self.blame.close()
+        if self.repoBrowser is not None:
+            self.repoBrowser.close()
+        if self.logBrowser is not None:
+            self.logBrowser.close()
+        
+    def vcsExists(self):
+        """
+        Public method used to test for the presence of the svn executable.
+        
+        @return flag indicating the existance (boolean) and an error message
+            (string)
+        """
+        self.versionStr = ".".join([str(v) for v in pysvn.svn_version[:-1]])
+        self.version = pysvn.svn_version[:-1]
+        return True, ""
+        
+    def vcsInit(self, vcsDir, noDialog=False):
+        """
+        Public method used to initialize the subversion repository.
+        
+        The subversion repository has to be initialized from outside eric6
+        because the respective command always works locally. Therefore we
+        always return TRUE without doing anything.
+        
+        @param vcsDir name of the VCS directory (string)
+        @param noDialog flag indicating quiet operations (boolean)
+        @return always TRUE
+        """
+        return True
+        
+    def vcsConvertProject(self, vcsDataDict, project):
+        """
+        Public method to convert an uncontrolled project to a version
+        controlled project.
+        
+        @param vcsDataDict dictionary of data required for the conversion
+        @param project reference to the project object
+        """
+        success = self.vcsImport(vcsDataDict, project.ppath)[0]
+        if not success:
+            E5MessageBox.critical(
+                self.__ui,
+                self.tr("Create project in repository"),
+                self.tr(
+                    """The project could not be created in the repository."""
+                    """ Maybe the given repository doesn't exist or the"""
+                    """ repository server is down."""))
+        else:
+            cwdIsPpath = False
+            if os.getcwd() == project.ppath:
+                os.chdir(os.path.dirname(project.ppath))
+                cwdIsPpath = True
+            tmpProjectDir = "{0}_tmp".format(project.ppath)
+            shutil.rmtree(tmpProjectDir, True)
+            os.rename(project.ppath, tmpProjectDir)
+            os.makedirs(project.ppath)
+            self.vcsCheckout(vcsDataDict, project.ppath)
+            if cwdIsPpath:
+                os.chdir(project.ppath)
+            self.vcsCommit(project.ppath, vcsDataDict["message"], True)
+            pfn = project.pfile
+            if not os.path.isfile(pfn):
+                pfn += "z"
+            if not os.path.isfile(pfn):
+                E5MessageBox.critical(
+                    self.__ui,
+                    self.tr("New project"),
+                    self.tr(
+                        """The project could not be checked out of the"""
+                        """ repository.<br />"""
+                        """Restoring the original contents."""))
+                if os.getcwd() == project.ppath:
+                    os.chdir(os.path.dirname(project.ppath))
+                    cwdIsPpath = True
+                else:
+                    cwdIsPpath = False
+                shutil.rmtree(project.ppath, True)
+                os.rename(tmpProjectDir, project.ppath)
+                project.pdata["VCS"] = 'None'
+                project.vcs = None
+                project.setDirty(True)
+                project.saveProject()
+                project.closeProject()
+                return
+            shutil.rmtree(tmpProjectDir, True)
+            project.closeProject(noSave=True)
+            project.openProject(pfn)
+        
+    def vcsImport(self, vcsDataDict, projectDir, noDialog=False):
+        """
+        Public method used to import the project into the Subversion
+        repository.
+        
+        @param vcsDataDict dictionary of data required for the import
+        @param projectDir project directory (string)
+        @param noDialog flag indicating quiet operations
+        @return flag indicating an execution without errors (boolean)
+            and a flag indicating the version controll status (boolean)
+        """
+        noDialog = False
+        msg = vcsDataDict["message"]
+        if not msg:
+            msg = '***'
+        
+        vcsDir = self.svnNormalizeURL(vcsDataDict["url"])
+        if vcsDir.startswith('/'):
+            vcsDir = 'file://{0}'.format(vcsDir)
+        elif vcsDir[1] in ['|', ':']:
+            vcsDir = 'file:///{0}'.format(vcsDir)
+        
+        project = vcsDir[vcsDir.rfind('/') + 1:]
+        
+        # create the dir structure to be imported into the repository
+        tmpDir = '{0}_tmp'.format(projectDir)
+        try:
+            os.makedirs(tmpDir)
+            if self.otherData["standardLayout"]:
+                os.mkdir(os.path.join(tmpDir, project))
+                os.mkdir(os.path.join(tmpDir, project, 'branches'))
+                os.mkdir(os.path.join(tmpDir, project, 'tags'))
+                shutil.copytree(
+                    projectDir, os.path.join(tmpDir, project, 'trunk'))
+            else:
+                shutil.copytree(projectDir, os.path.join(tmpDir, project))
+        except OSError:
+            if os.path.isdir(tmpDir):
+                shutil.rmtree(tmpDir, True)
+            return False, False
+        
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        cwd = os.getcwd()
+        os.chdir(os.path.join(tmpDir, project))
+        opts = self.options['global']
+        recurse = "--non-recursive" not in opts
+        url = self.__svnURL(vcsDir)
+        client = self.getClient()
+        if not noDialog:
+            dlg = SvnDialog(
+                self.tr('Importing project into Subversion repository'),
+                "import{0} --message {1} .".format(
+                    (not recurse) and " --non-recursive" or "", msg),
+                client)
+            QApplication.processEvents()
+        try:
+            rev = client.import_(".", url, msg, recurse, ignore=True)
+            status = True
+        except pysvn.ClientError as e:
+            status = False
+            rev = None
+            if not noDialog:
+                dlg.showError(e.args[0])
+        locker.unlock()
+        if not noDialog:
+            rev and dlg.showMessage(self.tr("Imported revision {0}.\n")
+                                    .format(rev.number))
+            dlg.finish()
+            dlg.exec_()
+        os.chdir(cwd)
+        
+        shutil.rmtree(tmpDir, True)
+        return status, False
+        
+    def vcsCheckout(self, vcsDataDict, projectDir, noDialog=False):
+        """
+        Public method used to check the project out of the Subversion
+        repository.
+        
+        @param vcsDataDict dictionary of data required for the checkout
+        @param projectDir project directory to create (string)
+        @param noDialog flag indicating quiet operations
+        @return flag indicating an execution without errors (boolean)
+        """
+        noDialog = False
+        try:
+            tag = vcsDataDict["tag"]
+        except KeyError:
+            tag = None
+        vcsDir = self.svnNormalizeURL(vcsDataDict["url"])
+        if vcsDir.startswith('/'):
+            vcsDir = 'file://{0}'.format(vcsDir)
+        elif vcsDir[1] in ['|', ':']:
+            vcsDir = 'file:///{0}'.format(vcsDir)
+            
+        if self.otherData["standardLayout"]:
+            if tag is None or tag == '':
+                svnUrl = '{0}/trunk'.format(vcsDir)
+            else:
+                if not tag.startswith('tags') and \
+                        not tag.startswith('branches'):
+                    type_, ok = QInputDialog.getItem(
+                        None,
+                        self.tr("Subversion Checkout"),
+                        self.tr(
+                            "The tag must be a normal tag (tags) or"
+                            " a branch tag (branches)."
+                            " Please select from the list."),
+                        self.tagTypeList,
+                        0, False)
+                    if not ok:
+                        return False
+                    tag = '{0}/{1}'.format(type_, tag)
+                svnUrl = '{0}/{1}'.format(vcsDir, tag)
+        else:
+            svnUrl = vcsDir
+        
+        opts = self.options['global'] + self.options['checkout']
+        recurse = "--non-recursive" not in opts
+        url = self.__svnURL(svnUrl)
+        client = self.getClient()
+        if not noDialog:
+            dlg = SvnDialog(
+                self.tr('Checking project out of Subversion repository'),
+                "checkout{0} {1} {2}".format(
+                    (not recurse) and " --non-recursive" or "",
+                    url, projectDir),
+                client)
+            QApplication.processEvents()
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        try:
+            client.checkout(url, projectDir, recurse)
+            status = True
+        except pysvn.ClientError as e:
+            status = False
+            if not noDialog:
+                dlg.showError(e.args[0])
+        locker.unlock()
+        if not noDialog:
+            dlg.finish()
+            dlg.exec_()
+        return status
+        
+    def vcsExport(self, vcsDataDict, projectDir):
+        """
+        Public method used to export a directory from the Subversion
+        repository.
+        
+        @param vcsDataDict dictionary of data required for the checkout
+        @param projectDir project directory to create (string)
+        @return flag indicating an execution without errors (boolean)
+        """
+        try:
+            tag = vcsDataDict["tag"]
+        except KeyError:
+            tag = None
+        vcsDir = self.svnNormalizeURL(vcsDataDict["url"])
+        if vcsDir.startswith('/') or vcsDir[1] == '|':
+            vcsDir = 'file://{0}'.format(vcsDir)
+            
+        if self.otherData["standardLayout"]:
+            if tag is None or tag == '':
+                svnUrl = '{0}/trunk'.format(vcsDir)
+            else:
+                if not tag.startswith('tags') and \
+                        not tag.startswith('branches'):
+                    type_, ok = QInputDialog.getItem(
+                        None,
+                        self.tr("Subversion Export"),
+                        self.tr(
+                            "The tag must be a normal tag (tags) or"
+                            " a branch tag (branches)."
+                            " Please select from the list."),
+                        self.tagTypeList,
+                        0, False)
+                    if not ok:
+                        return False
+                    tag = '{0}/{1}'.format(type_, tag)
+                svnUrl = '{0}/{1}'.format(vcsDir, tag)
+        else:
+            svnUrl = vcsDir
+        
+        opts = self.options['global']
+        recurse = "--non-recursive" not in opts
+        url = self.__svnURL(svnUrl)
+        client = self.getClient()
+        dlg = SvnDialog(
+            self.tr('Exporting project from Subversion repository'),
+            "export --force{0} {1} {2}".format(
+                (not recurse) and " --non-recursive" or "",
+                url, projectDir),
+            client)
+        QApplication.processEvents()
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        try:
+            client.export(url, projectDir, force=True, recurse=recurse)
+            status = True
+        except pysvn.ClientError as e:
+            status = False
+            dlg.showError(e.args[0])
+        locker.unlock()
+        dlg.finish()
+        dlg.exec_()
+        return status
+        
+    def vcsCommit(self, name, message, noDialog=False):
+        """
+        Public method used to make the change of a file/directory permanent
+        in the Subversion repository.
+        
+        @param name file/directory name to be committed (string or
+        list of strings)
+        @param message message for this operation (string)
+        @param noDialog flag indicating quiet operations
+        """
+        if not noDialog and not message:
+            # call CommitDialog and get message from there
+            if self.__commitDialog is None:
+                from .SvnCommitDialog import SvnCommitDialog
+                self.__commitDialog = SvnCommitDialog(
+                    self.svnGetChangelists(), self.__ui)
+                self.__commitDialog.accepted.connect(self.__vcsCommit_Step2)
+            self.__commitDialog.show()
+            self.__commitDialog.raise_()
+            self.__commitDialog.activateWindow()
+        
+        self.__commitData["name"] = name
+        self.__commitData["msg"] = message
+        self.__commitData["noDialog"] = noDialog
+        
+        if noDialog:
+            self.__vcsCommit_Step2()
+        
+    def __vcsCommit_Step2(self):
+        """
+        Private slot performing the second step of the commit action.
+        """
+        name = self.__commitData["name"]
+        msg = self.__commitData["msg"]
+        noDialog = self.__commitData["noDialog"]
+        
+        if not noDialog:
+            # check, if there are unsaved changes, that should be committed
+            if isinstance(name, list):
+                nameList = name
+            else:
+                nameList = [name]
+            ok = True
+            for nam in nameList:
+                # check for commit of the project
+                if os.path.isdir(nam):
+                    project = e5App().getObject("Project")
+                    if nam == project.getProjectPath():
+                        ok &= project.checkAllScriptsDirty(
+                            reportSyntaxErrors=True) and \
+                            project.checkDirty()
+                        continue
+                elif os.path.isfile(nam):
+                    editor = e5App().getObject("ViewManager")\
+                        .getOpenEditor(nam)
+                    if editor:
+                        ok &= editor.checkDirty()
+                if not ok:
+                    break
+            
+            if not ok:
+                res = E5MessageBox.yesNo(
+                    self.__ui,
+                    self.tr("Commit Changes"),
+                    self.tr(
+                        """The commit affects files, that have unsaved"""
+                        """ changes. Shall the commit be continued?"""),
+                    icon=E5MessageBox.Warning)
+                if not res:
+                    return
+        
+        if self.__commitDialog is not None:
+            msg = self.__commitDialog.logMessage()
+            if self.__commitDialog.hasChangelists():
+                changelists, keepChangelists = \
+                    self.__commitDialog.changelistsData()
+            else:
+                changelists, keepChangelists = [], False
+            self.__commitDialog.deleteLater()
+            self.__commitDialog = None
+        else:
+            changelists, keepChangelists = [], False
+        
+        if not msg:
+            msg = '***'
+        
+        if isinstance(name, list):
+            dname, fnames = self.splitPathList(name)
+        else:
+            dname, fname = self.splitPath(name)
+            fnames = [fname]
+        
+        if self.svnGetReposName(dname).startswith('http') or \
+           self.svnGetReposName(dname).startswith('svn'):
+            noDialog = False
+        
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        cwd = os.getcwd()
+        os.chdir(dname)
+        opts = self.options['global'] + self.options['commit']
+        recurse = "--non-recursive" not in opts
+        keeplocks = "--keep-locks" in opts
+        client = self.getClient()
+        if not noDialog:
+            dlg = SvnDialog(
+                self.tr('Commiting changes to Subversion repository'),
+                "commit{0}{1}{2}{3} --message {4} {5}".format(
+                    (not recurse) and " --non-recursive" or "",
+                    keeplocks and " --keep-locks" or "",
+                    keepChangelists and " --keep-changelists" or "",
+                    changelists and
+                    " --changelist ".join([""] + changelists) or "",
+                    msg, " ".join(fnames)),
+                client)
+            QApplication.processEvents()
+        try:
+            if changelists:
+                rev = client.checkin(fnames, msg,
+                                     recurse=recurse, keep_locks=keeplocks,
+                                     keep_changelist=keepChangelists,
+                                     changelists=changelists)
+            else:
+                rev = client.checkin(fnames, msg,
+                                     recurse=recurse, keep_locks=keeplocks)
+        except pysvn.ClientError as e:
+            rev = None
+            if not noDialog:
+                dlg.showError(e.args[0])
+        locker.unlock()
+        if not noDialog:
+            rev and dlg.showMessage(self.tr("Committed revision {0}.")
+                                    .format(rev.number))
+            dlg.finish()
+            dlg.exec_()
+        os.chdir(cwd)
+        self.committed.emit()
+        self.checkVCSStatus()
+        
+    def vcsUpdate(self, name, noDialog=False):
+        """
+        Public method used to update a file/directory with the Subversion
+        repository.
+        
+        @param name file/directory name to be updated (string or list of
+            strings)
+        @param noDialog flag indicating quiet operations (boolean)
+        @return flag indicating, that the update contained an add
+            or delete (boolean)
+        """
+        if isinstance(name, list):
+            dname, fnames = self.splitPathList(name)
+        else:
+            dname, fname = self.splitPath(name)
+            fnames = [fname]
+        
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        cwd = os.getcwd()
+        os.chdir(dname)
+        opts = self.options['global'] + self.options['update']
+        recurse = "--non-recursive" not in opts
+        client = self.getClient()
+        if not noDialog:
+            dlg = SvnDialog(
+                self.tr('Synchronizing with the Subversion repository'),
+                "update{0} {1}".format(
+                    (not recurse) and " --non-recursive" or "",
+                    " ".join(fnames)),
+                client)
+        QApplication.processEvents()
+        try:
+            client.update(fnames, recurse)
+        except pysvn.ClientError as e:
+            dlg.showError(e.args[0])
+        locker.unlock()
+        if not noDialog:
+            dlg.finish()
+            dlg.exec_()
+            res = dlg.hasAddOrDelete()
+        else:
+            res = False
+        os.chdir(cwd)
+        self.checkVCSStatus()
+        return res
+        
+    def vcsAdd(self, name, isDir=False, noDialog=False):
+        """
+        Public method used to add a file/directory to the Subversion
+        repository.
+        
+        @param name file/directory name to be added (string)
+        @param isDir flag indicating name is a directory (boolean)
+        @param noDialog flag indicating quiet operations (boolean)
+        """
+        if isinstance(name, list):
+            if isDir:
+                dname, fname = os.path.split(name[0])
+            else:
+                dname, fnames = self.splitPathList(name)
+        else:
+            if isDir:
+                dname, fname = os.path.split(name)
+            else:
+                dname, fname = self.splitPath(name)
+        names = []
+        tree = []
+        wdir = dname
+        if self.__wcng:
+            repodir = dname
+            while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+                repodir = os.path.dirname(repodir)
+                if os.path.splitdrive(repodir)[1] == os.sep:
+                    return  # oops, project is not version controlled
+            while os.path.normcase(dname) != os.path.normcase(repodir) and \
+                (os.path.normcase(dname) not in self.statusCache or
+                 self.statusCache[os.path.normcase(dname)] ==
+                    self.canBeAdded):
+                # add directories recursively, if they aren't in the
+                # repository already
+                tree.insert(-1, dname)
+                dname = os.path.dirname(dname)
+                wdir = dname
+        else:
+            while not os.path.exists(os.path.join(dname, self.adminDir)):
+                # add directories recursively, if they aren't in the
+                # repository already
+                tree.insert(-1, dname)
+                dname = os.path.dirname(dname)
+                wdir = dname
+        names.extend(tree)
+        
+        if isinstance(name, list):
+            tree2 = []
+            for n in name:
+                d = os.path.dirname(n)
+                if self.__wcng:
+                    repodir = d
+                    while not os.path.isdir(
+                            os.path.join(repodir, self.adminDir)):
+                        repodir = os.path.dirname(repodir)
+                        if os.path.splitdrive(repodir)[1] == os.sep:
+                            return  # oops, project is not version controlled
+                    while (os.path.normcase(d) !=
+                            os.path.normcase(repodir)) and \
+                        (d not in tree2 + tree) and \
+                        (os.path.normcase(d) not in self.statusCache or
+                         self.statusCache[os.path.normcase(d)] ==
+                            self.canBeAdded):
+                        tree2.append(d)
+                        d = os.path.dirname(d)
+                else:
+                    while not os.path.exists(os.path.join(d, self.adminDir)):
+                        if d in tree2 + tree:
+                            break
+                        tree2.append(d)
+                        d = os.path.dirname(d)
+            tree2.reverse()
+            names.extend(tree2)
+            names.extend(name)
+        else:
+            names.append(name)
+        
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        cwd = os.getcwd()
+        os.chdir(wdir)
+        opts = self.options['global'] + self.options['add']
+        recurse = False
+        force = "--force" in opts or noDialog
+        noignore = "--no-ignore" in opts
+        client = self.getClient()
+        if not noDialog:
+            dlg = SvnDialog(
+                self.tr('Adding files/directories to the Subversion'
+                        ' repository'),
+                "add --non-recursive{0}{1} {2}".format(
+                    force and " --force" or "",
+                    noignore and " --no-ignore" or "",
+                    " ".join(names)),
+                client)
+            QApplication.processEvents()
+        try:
+            client.add(names, recurse=recurse, force=force,
+                       ignore=not noignore)
+        except pysvn.ClientError as e:
+            if not noDialog:
+                dlg.showError(e.args[0])
+        locker.unlock()
+        if not noDialog:
+            dlg.finish()
+            dlg.exec_()
+        os.chdir(cwd)
+        
+    def vcsAddBinary(self, name, isDir=False):
+        """
+        Public method used to add a file/directory in binary mode to the
+        Subversion repository.
+        
+        @param name file/directory name to be added (string)
+        @param isDir flag indicating name is a directory (boolean)
+        """
+        self.vcsAdd(name, isDir)
+        
+    def vcsAddTree(self, path):
+        """
+        Public method to add a directory tree rooted at path to the Subversion
+        repository.
+        
+        @param path root directory of the tree to be added (string or list of
+            strings))
+        """
+        tree = []
+        if isinstance(path, list):
+            dname, fnames = self.splitPathList(path)
+            for n in path:
+                d = os.path.dirname(n)
+                if self.__wcng:
+                    repodir = d
+                    while not os.path.isdir(
+                            os.path.join(repodir, self.adminDir)):
+                        repodir = os.path.dirname(repodir)
+                        if os.path.splitdrive(repodir)[1] == os.sep:
+                            return  # oops, project is not version controlled
+                    while (os.path.normcase(d) !=
+                            os.path.normcase(repodir)) and \
+                        (d not in tree) and \
+                        (os.path.normcase(d) not in self.statusCache or
+                         self.statusCache[os.path.normcase(d)] ==
+                            self.canBeAdded):
+                        tree.append(d)
+                        d = os.path.dirname(d)
+                else:
+                    while not os.path.exists(os.path.join(d, self.adminDir)):
+                        # add directories recursively,
+                        # if they aren't in the repository already
+                        if d in tree:
+                            break
+                        tree.append(d)
+                        d = os.path.dirname(d)
+            tree.reverse()
+        else:
+            dname, fname = os.path.split(path)
+            if self.__wcng:
+                repodir = dname
+                while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+                    repodir = os.path.dirname(repodir)
+                    if os.path.splitdrive(repodir)[1] == os.sep:
+                        return  # oops, project is not version controlled
+                while (os.path.normcase(dname) !=
+                        os.path.normcase(repodir)) and \
+                    (os.path.normcase(dname) not in self.statusCache or
+                     self.statusCache[os.path.normcase(dname)] ==
+                        self.canBeAdded):
+                    # add directories recursively, if they aren't in the
+                    # repository already
+                    tree.insert(-1, dname)
+                    dname = os.path.dirname(dname)
+            else:
+                while not os.path.exists(os.path.join(dname, self.adminDir)):
+                    # add directories recursively,
+                    # if they aren't in the repository already
+                    tree.insert(-1, dname)
+                    dname = os.path.dirname(dname)
+        if tree:
+            self.vcsAdd(tree, True)
+        
+        names = []
+        if isinstance(path, list):
+            names.extend(path)
+        else:
+            names.append(path)
+        
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        cwd = os.getcwd()
+        os.chdir(dname)
+        opts = self.options['global'] + self.options['add']
+        recurse = True
+        force = "--force" in opts
+        ignore = "--ignore" in opts
+        client = self.getClient()
+        dlg = SvnDialog(
+            self.tr('Adding directory trees to the Subversion repository'),
+            "add{0}{1} {2}".format(
+                force and " --force" or "",
+                ignore and " --ignore" or "",
+                " ".join(names)),
+            client)
+        QApplication.processEvents()
+        try:
+            client.add(names, recurse=recurse, force=force, ignore=ignore)
+        except pysvn.ClientError as e:
+            dlg.showError(e.args[0])
+        locker.unlock()
+        dlg.finish()
+        dlg.exec_()
+        os.chdir(cwd)
+        
+    def vcsRemove(self, name, project=False, noDialog=False):
+        """
+        Public method used to remove a file/directory from the Subversion
+        repository.
+        
+        The default operation is to remove the local copy as well.
+        
+        @param name file/directory name to be removed (string or list of
+            strings))
+        @param project flag indicating deletion of a project tree (boolean)
+            (not needed)
+        @param noDialog flag indicating quiet operations
+        @return flag indicating successfull operation (boolean)
+        """
+        if not isinstance(name, list):
+            name = [name]
+        opts = self.options['global'] + self.options['remove']
+        force = "--force" in opts or noDialog
+        client = self.getClient()
+        if not noDialog:
+            dlg = SvnDialog(
+                self.tr('Removing files/directories from the Subversion'
+                        ' repository'),
+                "remove{0} {1}".format(
+                    force and " --force" or "",
+                    " ".join(name)),
+                client)
+            QApplication.processEvents()
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        try:
+            client.remove(name, force=force)
+            res = True
+        except pysvn.ClientError as e:
+            res = False
+            if not noDialog:
+                dlg.showError(e.args[0])
+        locker.unlock()
+        if not noDialog:
+            dlg.finish()
+            dlg.exec_()
+        
+        return res
+        
+    def vcsMove(self, name, project, target=None, noDialog=False):
+        """
+        Public method used to move a file/directory.
+        
+        @param name file/directory name to be moved (string)
+        @param project reference to the project object
+        @param target new name of the file/directory (string)
+        @param noDialog flag indicating quiet operations
+        @return flag indicating successfull operation (boolean)
+        """
+        rx_prot = QRegExp('(file:|svn:|svn+ssh:|http:|https:).+')
+        opts = self.options['global']
+        res = False
+        
+        if noDialog:
+            if target is None:
+                return False
+            force = True
+            accepted = True
+        else:
+            from .SvnCopyDialog import SvnCopyDialog
+            dlg = SvnCopyDialog(name, None, True, "--force" in opts)
+            accepted = (dlg.exec_() == QDialog.Accepted)
+            if accepted:
+                target, force = dlg.getData()
+            if not target:
+                return False
+        
+        if not rx_prot.exactMatch(target):
+            isDir = os.path.isdir(name)
+        else:
+            isDir = False
+        
+        if accepted:
+            client = self.getClient()
+            if rx_prot.exactMatch(target):
+                target = self.__svnURL(target)
+                log = "Moving {0} to {1}".format(name, target)
+            else:
+                log = ""
+                target = target
+            if not noDialog:
+                dlg = \
+                    SvnDialog(
+                        self.tr('Moving {0}').format(name),
+                        "move{0}{1} {2} {3}".format(
+                            force and " --force" or "",
+                            log and (" --message {0}".format(log)) or "",
+                            name, target),
+                        client, log=log)
+                QApplication.processEvents()
+            locker = QMutexLocker(self.vcsExecutionMutex)
+            try:
+                client.move(name, target, force=force)
+                res = True
+            except pysvn.ClientError as e:
+                res = False
+                if not noDialog:
+                    dlg.showError(e.args[0])
+            locker.unlock()
+            if not noDialog:
+                dlg.finish()
+                dlg.exec_()
+            if res and not rx_prot.exactMatch(target):
+                if target.startswith(project.getProjectPath()):
+                    if isDir:
+                        project.moveDirectory(name, target)
+                    else:
+                        project.renameFileInPdata(name, target)
+                else:
+                    if isDir:
+                        project.removeDirectory(name)
+                    else:
+                        project.removeFile(name)
+        return res
+        
+    def vcsDiff(self, name):
+        """
+        Public method used to view the difference of a file/directory to the
+        Subversion repository.
+        
+        If name is a directory and is the project directory, all project files
+        are saved first. If name is a file (or list of files), which is/are
+        being edited and has unsaved modification, they can be saved or the
+        operation may be aborted.
+        
+        @param name file/directory name to be diffed (string)
+        """
+        if isinstance(name, list):
+            names = name[:]
+        else:
+            names = [name]
+        for nam in names:
+            if os.path.isfile(nam):
+                editor = e5App().getObject("ViewManager").getOpenEditor(nam)
+                if editor and not editor.checkDirty():
+                    return
+            else:
+                project = e5App().getObject("Project")
+                if nam == project.ppath and not project.saveAllScripts():
+                    return
+        if self.diff is None:
+            from .SvnDiffDialog import SvnDiffDialog
+            self.diff = SvnDiffDialog(self)
+        self.diff.show()
+        self.diff.raise_()
+        QApplication.processEvents()
+        self.diff.start(name, refreshable=True)
+        
+    def vcsStatus(self, name):
+        """
+        Public method used to view the status of files/directories in the
+        Subversion repository.
+        
+        @param name file/directory name(s) to show the status of
+            (string or list of strings)
+        """
+        if self.status is None:
+            from .SvnStatusDialog import SvnStatusDialog
+            self.status = SvnStatusDialog(self)
+        self.status.show()
+        self.status.raise_()
+        QApplication.processEvents()
+        self.status.start(name)
+        
+    def vcsTag(self, name):
+        """
+        Public method used to set the tag of a file/directory in the
+        Subversion repository.
+        
+        @param name file/directory name to be tagged (string)
+        """
+        dname, fname = self.splitPath(name)
+        
+        reposURL = self.svnGetReposName(dname)
+        if reposURL is None:
+            E5MessageBox.critical(
+                self.__ui,
+                self.tr("Subversion Error"),
+                self.tr(
+                    """The URL of the project repository could not be"""
+                    """ retrieved from the working copy. The tag operation"""
+                    """ will be aborted"""))
+            return
+        
+        if self.otherData["standardLayout"]:
+            url = None
+        else:
+            url = self.svnNormalizeURL(reposURL)
+        from .SvnTagDialog import SvnTagDialog
+        dlg = SvnTagDialog(self.allTagsBranchesList, url,
+                           self.otherData["standardLayout"])
+        if dlg.exec_() == QDialog.Accepted:
+            tag, tagOp = dlg.getParameters()
+            if tag in self.allTagsBranchesList:
+                self.allTagsBranchesList.remove(tag)
+            self.allTagsBranchesList.insert(0, tag)
+        else:
+            return
+        
+        if self.otherData["standardLayout"]:
+            rx_base = QRegExp('(.+)/(trunk|tags|branches).*')
+            if not rx_base.exactMatch(reposURL):
+                E5MessageBox.critical(
+                    self.__ui,
+                    self.tr("Subversion Error"),
+                    self.tr(
+                        """The URL of the project repository has an"""
+                        """ invalid format. The tag operation will"""
+                        """ be aborted"""))
+                return
+            
+            reposRoot = rx_base.cap(1)
+            if tagOp in [1, 4]:
+                url = '{0}/tags/{1}'.format(reposRoot, Utilities.quote(tag))
+            elif tagOp in [2, 8]:
+                url = '{0}/branches/{1}'.format(
+                    reposRoot, Utilities.quote(tag))
+        else:
+            url = self.__svnURL(tag)
+        
+        self.tagName = tag
+        client = self.getClient()
+        rev = None
+        if tagOp in [1, 2]:
+            log = 'Created tag <{0}>'.format(self.tagName)
+            dlg = SvnDialog(
+                self.tr('Tagging {0} in the Subversion repository')
+                    .format(name),
+                "copy --message {0} {1} {2}".format(log, reposURL, url),
+                client, log=log)
+            QApplication.processEvents()
+            locker = QMutexLocker(self.vcsExecutionMutex)
+            try:
+                rev = client.copy(reposURL, url)
+            except pysvn.ClientError as e:
+                dlg.showError(e.args[0])
+            locker.unlock()
+        else:
+            log = 'Deleted tag <{0}>'.format(self.tagName)
+            dlg = SvnDialog(
+                self.tr('Tagging {0} in the Subversion repository')
+                    .format(name),
+                "remove --message {0} {1}".format(log, url),
+                client, log=log)
+            QApplication.processEvents()
+            locker = QMutexLocker(self.vcsExecutionMutex)
+            try:
+                rev = client.remove(url)
+            except pysvn.ClientError as e:
+                dlg.showError(e.args[0])
+            locker.unlock()
+        rev and dlg.showMessage(
+            self.tr("Revision {0}.\n").format(rev.number))
+        dlg.finish()
+        dlg.exec_()
+        
+    def vcsRevert(self, name):
+        """
+        Public method used to revert changes made to a file/directory.
+        
+        @param name file/directory name to be reverted (string)
+        """
+        recurse = False
+        if not isinstance(name, list):
+            name = [name]
+            if os.path.isdir(name[0]):
+                recurse = True
+        
+        project = e5App().getObject("Project")
+        names = [project.getRelativePath(nam) for nam in name]
+        if names[0]:
+            from UI.DeleteFilesConfirmationDialog import \
+                DeleteFilesConfirmationDialog
+            dia = DeleteFilesConfirmationDialog(
+                self.parent(),
+                self.tr("Revert changes"),
+                self.tr(
+                    "Do you really want to revert all changes to these files"
+                    " or directories?"),
+                name)
+            yes = dia.exec_() == QDialog.Accepted
+        else:
+            yes = E5MessageBox.yesNo(
+                None,
+                self.tr("Revert changes"),
+                self.tr("""Do you really want to revert all changes of"""
+                        """ the project?"""))
+        if yes:
+            client = self.getClient()
+            dlg = SvnDialog(
+                self.tr('Reverting changes'),
+                "revert {0} {1}".format(
+                    (not recurse) and " --non-recursive" or "",
+                    " ".join(name)),
+                client)
+            QApplication.processEvents()
+            locker = QMutexLocker(self.vcsExecutionMutex)
+            try:
+                client.revert(name, recurse)
+            except pysvn.ClientError as e:
+                dlg.showError(e.args[0])
+            locker.unlock()
+            dlg.finish()
+            dlg.exec_()
+            self.checkVCSStatus()
+    
+    def vcsSwitch(self, name):
+        """
+        Public method used to switch a directory to a different tag/branch.
+        
+        @param name directory name to be switched (string)
+        @return flag indicating, that the switch contained an add
+            or delete (boolean)
+        """
+        dname, fname = self.splitPath(name)
+        
+        reposURL = self.svnGetReposName(dname)
+        if reposURL is None:
+            E5MessageBox.critical(
+                self.__ui,
+                self.tr("Subversion Error"),
+                self.tr(
+                    """The URL of the project repository could not be"""
+                    """ retrieved from the working copy. The switch"""
+                    """ operation will be aborted"""))
+            return False
+        
+        if self.otherData["standardLayout"]:
+            url = None
+        else:
+            url = self.svnNormalizeURL(reposURL)
+        from .SvnSwitchDialog import SvnSwitchDialog
+        dlg = SvnSwitchDialog(self.allTagsBranchesList, url,
+                              self.otherData["standardLayout"])
+        if dlg.exec_() == QDialog.Accepted:
+            tag, tagType = dlg.getParameters()
+            if tag in self.allTagsBranchesList:
+                self.allTagsBranchesList.remove(tag)
+            self.allTagsBranchesList.insert(0, tag)
+        else:
+            return False
+        
+        if self.otherData["standardLayout"]:
+            rx_base = QRegExp('(.+)/(trunk|tags|branches).*')
+            if not rx_base.exactMatch(reposURL):
+                E5MessageBox.critical(
+                    self.__ui,
+                    self.tr("Subversion Error"),
+                    self.tr(
+                        """The URL of the project repository has an"""
+                        """ invalid format. The switch operation will"""
+                        """ be aborted"""))
+                return False
+            
+            reposRoot = rx_base.cap(1)
+            tn = tag
+            if tagType == 1:
+                url = '{0}/tags/{1}'.format(reposRoot, Utilities.quote(tag))
+            elif tagType == 2:
+                url = '{0}/branches/{1}'.format(
+                    reposRoot, Utilities.quote(tag))
+            elif tagType == 4:
+                url = '{0}/trunk'.format(reposRoot)
+                tn = 'HEAD'
+        else:
+            url = self.__svnURL(tag)
+            tn = url
+        
+        client = self.getClient()
+        dlg = SvnDialog(self.tr('Switching to {0}').format(tn),
+                        "switch {0} {1}".format(url, name),
+                        client)
+        QApplication.processEvents()
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        try:
+            rev = client.switch(name, url)
+            dlg.showMessage(self.tr("Revision {0}.\n").format(rev.number))
+        except pysvn.ClientError as e:
+            dlg.showError(e.args[0])
+        locker.unlock()
+        dlg.finish()
+        dlg.exec_()
+        res = dlg.hasAddOrDelete()
+        self.checkVCSStatus()
+        return res
+        
+    def vcsMerge(self, name):
+        """
+        Public method used to merge a URL/revision into the local project.
+        
+        @param name file/directory name to be merged (string)
+        """
+        dname, fname = self.splitPath(name)
+        
+        opts = self.options['global']
+        from .SvnMergeDialog import SvnMergeDialog
+        dlg = SvnMergeDialog(self.mergeList[0], self.mergeList[1],
+                             self.mergeList[2], "--force" in opts)
+        if dlg.exec_() == QDialog.Accepted:
+            urlrev1, urlrev2, target, force = dlg.getParameters()
+        else:
+            return
+        
+        # remember URL or revision
+        if urlrev1 in self.mergeList[0]:
+            self.mergeList[0].remove(urlrev1)
+        self.mergeList[0].insert(0, urlrev1)
+        if urlrev2 in self.mergeList[1]:
+            self.mergeList[1].remove(urlrev2)
+        self.mergeList[1].insert(0, urlrev2)
+        
+        rx_rev = QRegExp('\\d+|HEAD|head')
+        
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        cwd = os.getcwd()
+        os.chdir(dname)
+        recurse = "--non-recursive" not in opts
+        if rx_rev.exactMatch(urlrev1):
+            if urlrev1 in ["HEAD", "head"]:
+                revision1 = pysvn.Revision(pysvn.opt_revision_kind.head)
+                rev1 = "HEAD"
+            else:
+                revision1 = pysvn.Revision(
+                    pysvn.opt_revision_kind.number, int(urlrev1))
+                rev1 = urlrev1
+            if urlrev2 in ["HEAD", "head"]:
+                revision2 = pysvn.Revision(pysvn.opt_revision_kind.head)
+                rev2 = "HEAD"
+            else:
+                revision2 = pysvn.Revision(
+                    pysvn.opt_revision_kind.number, int(urlrev2))
+                rev2 = urlrev2
+            if not target:
+                url1 = name
+                url2 = name
+            else:
+                url1 = target
+                url2 = target
+                
+            # remember target
+            if target in self.mergeList[2]:
+                self.mergeList[2].remove(target)
+            self.mergeList[2].insert(0, target)
+        else:
+            if "@" in urlrev1:
+                url1, rev = urlrev1.split("@")
+                if rev in ["HEAD", "head"]:
+                    revision1 = pysvn.Revision(pysvn.opt_revision_kind.head)
+                    rev1 = "HEAD"
+                else:
+                    revision1 = pysvn.Revision(
+                        pysvn.opt_revision_kind.number, int(rev))
+                    rev1 = rev
+            else:
+                url1 = urlrev1
+                revision1 = pysvn.Revision(pysvn.opt_revision_kind.unspecified)
+                rev1 = ""
+            if "@" in urlrev2:
+                url2, rev = urlrev2.split("@")
+                if rev in ["HEAD", "head"]:
+                    revision2 = pysvn.Revision(pysvn.opt_revision_kind.head)
+                    rev2 = "HEAD"
+                else:
+                    revision2 = pysvn.Revision(
+                        pysvn.opt_revision_kind.number, int(rev))
+                    rev2 = rev
+            else:
+                url2 = urlrev2
+                revision2 = pysvn.Revision(pysvn.opt_revision_kind.unspecified)
+                rev2 = ""
+        client = self.getClient()
+        dlg = \
+            SvnDialog(
+                self.tr('Merging {0}').format(name),
+                "merge{0}{1} {2} {3} {4}".format(
+                    (not recurse) and " --non-recursive" or "",
+                    force and " --force" or "",
+                    "{0}{1}".format(url1, rev1 and ("@" + rev1) or ""),
+                    "{0}{1}".format(url2, rev2 and ("@" + rev2) or ""),
+                    fname),
+                client)
+        QApplication.processEvents()
+        try:
+            client.merge(url1, revision1, url2, revision2, fname,
+                         recurse=recurse, force=force)
+        except pysvn.ClientError as e:
+            dlg.showError(e.args[0])
+        locker.unlock()
+        dlg.finish()
+        dlg.exec_()
+        os.chdir(cwd)
+        
+    def vcsRegisteredState(self, name):
+        """
+        Public method used to get the registered state of a file in the vcs.
+        
+        @param name filename to check (string)
+        @return a combination of canBeCommited and canBeAdded
+        """
+        if self.__wcng:
+            return self.__vcsRegisteredState_wcng(name)
+        else:
+            return self.__vcsRegisteredState_wc(name)
+        
+    def __vcsRegisteredState_wcng(self, name):
+        """
+        Private method used to get the registered state of a file in the vcs.
+        
+        This is the variant for subversion installations using the new
+        working copy meta-data format.
+        
+        @param name filename to check (string)
+        @return a combination of canBeCommited and canBeAdded
+        """
+        if name.endswith(os.sep):
+            name = name[:-1]
+        name = os.path.normcase(name)
+        dname, fname = self.splitPath(name)
+        
+        if fname == '.' and os.path.isdir(os.path.join(dname, self.adminDir)):
+            return self.canBeCommitted
+        
+        if name in self.statusCache:
+            return self.statusCache[name]
+        
+        name = os.path.normcase(name)
+        states = {name: 0}
+        states = self.vcsAllRegisteredStates(states, dname, False)
+        if states[name] == self.canBeCommitted:
+            return self.canBeCommitted
+        else:
+            return self.canBeAdded
+        
+    def __vcsRegisteredState_wc(self, name):
+        """
+        Private method used to get the registered state of a file in the vcs.
+        
+        This is the variant for subversion installations using the old working
+        copy meta-data format.
+        
+        @param name filename to check (string)
+        @return a combination of canBeCommited and canBeAdded
+        """
+        dname, fname = self.splitPath(name)
+        
+        if fname == '.':
+            if os.path.isdir(os.path.join(dname, self.adminDir)):
+                return self.canBeCommitted
+            else:
+                return self.canBeAdded
+        
+        name = os.path.normcase(name)
+        states = {name: 0}
+        states = self.vcsAllRegisteredStates(states, dname, False)
+        if states[name] == self.canBeCommitted:
+            return self.canBeCommitted
+        else:
+            return self.canBeAdded
+        
+    def vcsAllRegisteredStates(self, names, dname, shortcut=True):
+        """
+        Public method used to get the registered states of a number of files
+        in the vcs.
+        
+        <b>Note:</b> If a shortcut is to be taken, the code will only check,
+        if the named directory has been scanned already. If so, it is assumed,
+        that the states for all files has been populated by the previous run.
+        
+        @param names dictionary with all filenames to be checked as keys
+        @param dname directory to check in (string)
+        @param shortcut flag indicating a shortcut should be taken (boolean)
+        @return the received dictionary completed with a combination of
+            canBeCommited and canBeAdded or None in order to signal an error
+        """
+        if self.__wcng:
+            return self.__vcsAllRegisteredStates_wcng(names, dname, shortcut)
+        else:
+            return self.__vcsAllRegisteredStates_wc(names, dname, shortcut)
+        
+    def __vcsAllRegisteredStates_wcng(self, names, dname, shortcut=True):
+        """
+        Private method used to get the registered states of a number of files
+        in the vcs.
+        
+        This is the variant for subversion installations using the new working
+        copy meta-data format.
+        
+        <b>Note:</b> If a shortcut is to be taken, the code will only check,
+        if the named directory has been scanned already. If so, it is assumed,
+        that the states for all files has been populated by the previous run.
+        
+        @param names dictionary with all filenames to be checked as keys
+        @param dname directory to check in (string)
+        @param shortcut flag indicating a shortcut should be taken (boolean)
+        @return the received dictionary completed with a combination of
+            canBeCommited and canBeAdded or None in order to signal an error
+        """
+        if dname.endswith(os.sep):
+            dname = dname[:-1]
+        dname = os.path.normcase(dname)
+        
+        found = False
+        for name in self.statusCache.keys():
+            if name in names:
+                found = True
+                names[name] = self.statusCache[name]
+        
+        if not found:
+            # find the root of the repo
+            repodir = dname
+            while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+                repodir = os.path.dirname(repodir)
+                if os.path.splitdrive(repodir)[1] == os.sep:
+                    return names
+            
+            from .SvnDialogMixin import SvnDialogMixin
+            mixin = SvnDialogMixin()
+            client = self.getClient()
+            client.callback_get_login = \
+                mixin._clientLoginCallback
+            client.callback_ssl_server_trust_prompt = \
+                mixin._clientSslServerTrustPromptCallback
+            
+            try:
+                locker = QMutexLocker(self.vcsExecutionMutex)
+                allFiles = client.status(dname, recurse=True, get_all=True,
+                                         ignore=True, update=False)
+                locker.unlock()
+                dirs = [x for x in names.keys() if os.path.isdir(x)]
+                for file in allFiles:
+                    # file.path is always unicode in Python 2
+                    name = os.path.normcase(file.path)
+                    if self.__isVersioned(file):
+                        if name in names:
+                            names[name] = self.canBeCommitted
+                            dn = name
+                            while os.path.splitdrive(dn)[1] != os.sep and \
+                                    dn != repodir:
+                                dn = os.path.dirname(dn)
+                                if dn in self.statusCache and \
+                                   self.statusCache[dn] == self.canBeCommitted:
+                                    break
+                                self.statusCache[dn] = self.canBeCommitted
+                        self.statusCache[name] = self.canBeCommitted
+                        if dirs:
+                            for d in dirs:
+                                if name.startswith(d):
+                                    names[d] = self.canBeCommitted
+                                    self.statusCache[d] = self.canBeCommitted
+                                    dirs.remove(d)
+                                    break
+                    else:
+                        self.statusCache[name] = self.canBeAdded
+            except pysvn.ClientError:
+                locker.unlock()    # ignore pysvn errors
+        
+        return names
+        
+    def __vcsAllRegisteredStates_wc(self, names, dname, shortcut=True):
+        """
+        Private method used to get the registered states of a number of files
+        in the VCS.
+        
+        This is the variant for subversion installations using the old working
+        copy meta-data format.
+        
+        <b>Note:</b> If a shortcut is to be taken, the code will only check,
+        if the named directory has been scanned already. If so, it is assumed,
+        that the states for all files has been populated by the previous run.
+        
+        @param names dictionary with all filenames to be checked as keys
+        @param dname directory to check in (string)
+        @param shortcut flag indicating a shortcut should be taken (boolean)
+        @return the received dictionary completed with a combination of
+            canBeCommited and canBeAdded or None in order to signal an error
+        """
+        if not os.path.isdir(os.path.join(dname, self.adminDir)):
+            # not under version control -> do nothing
+            return names
+        
+        found = False
+        for name in self.statusCache:
+            if os.path.dirname(name) == dname:
+                if shortcut:
+                    found = True
+                    break
+                if name in names:
+                    found = True
+                    names[name] = self.statusCache[name]
+        
+        if not found:
+            from .SvnDialogMixin import SvnDialogMixin
+            mixin = SvnDialogMixin()
+            client = self.getClient()
+            client.callback_get_login = \
+                mixin._clientLoginCallback
+            client.callback_ssl_server_trust_prompt = \
+                mixin._clientSslServerTrustPromptCallback
+            
+            try:
+                locker = QMutexLocker(self.vcsExecutionMutex)
+                allFiles = client.status(dname, recurse=True, get_all=True,
+                                         ignore=True, update=False)
+                locker.unlock()
+                for file in allFiles:
+                    # file.path is always unicode in Python 2
+                    name = os.path.normcase(file.path)
+                    if self.__isVersioned(file):
+                        if name in names:
+                            names[name] = self.canBeCommitted
+                        self.statusCache[name] = self.canBeCommitted
+                    else:
+                        self.statusCache[name] = self.canBeAdded
+            except pysvn.ClientError:
+                locker.unlock()    # ignore pysvn errors
+        
+        return names
+        
+    def __isVersioned(self, status):
+        """
+        Private method to check, if the given status indicates a
+        versioned state.
+        
+        @param status status object to check (pysvn.PysvnStatus)
+        @return flag indicating a versioned state (boolean)
+        """
+        return status["text_status"] in [
+            pysvn.wc_status_kind.normal,
+            pysvn.wc_status_kind.added,
+            pysvn.wc_status_kind.missing,
+            pysvn.wc_status_kind.deleted,
+            pysvn.wc_status_kind.replaced,
+            pysvn.wc_status_kind.modified,
+            pysvn.wc_status_kind.merged,
+            pysvn.wc_status_kind.conflicted,
+        ]
+        
+    def clearStatusCache(self):
+        """
+        Public method to clear the status cache.
+        """
+        self.statusCache = {}
+        
+    def vcsInitConfig(self, project):
+        """
+        Public method to initialize the VCS configuration.
+        
+        This method ensures, that eric specific files and directories are
+        ignored.
+        
+        @param project reference to the project (Project)
+        """
+        configPath = getConfigPath()
+        if os.path.exists(configPath):
+            amendConfig()
+        else:
+            createDefaultConfig()
+    
+    def vcsName(self):
+        """
+        Public method returning the name of the vcs.
+        
+        @return always 'Subversion' (string)
+        """
+        return "Subversion"
+
+    def vcsCleanup(self, name):
+        """
+        Public method used to cleanup the working copy.
+        
+        @param name directory name to be cleaned up (string)
+        """
+        client = self.getClient()
+        dlg = SvnDialog(self.tr('Cleaning up {0}').format(name),
+                        "cleanup {0}".format(name),
+                        client)
+        QApplication.processEvents()
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        try:
+            client.cleanup(name)
+        except pysvn.ClientError as e:
+            dlg.showError(e.args[0])
+        locker.unlock()
+        dlg.finish()
+        dlg.exec_()
+    
+    def vcsCommandLine(self, name):
+        """
+        Public method used to execute arbitrary subversion commands.
+        
+        @param name directory name of the working directory (string)
+        """
+        from .SvnCommandDialog import SvnCommandDialog
+        dlg = SvnCommandDialog(self.commandHistory, self.wdHistory, name)
+        if dlg.exec_() == QDialog.Accepted:
+            command, wd = dlg.getData()
+            commandList = Utilities.parseOptionString(command)
+            
+            # This moves any previous occurrence of these arguments to the head
+            # of the list.
+            if command in self.commandHistory:
+                self.commandHistory.remove(command)
+            self.commandHistory.insert(0, command)
+            if wd in self.wdHistory:
+                self.wdHistory.remove(wd)
+            self.wdHistory.insert(0, wd)
+            
+            args = []
+            self.addArguments(args, commandList)
+            
+            from Plugins.VcsPlugins.vcsSubversion.SvnDialog import \
+                SvnDialog as SvnProcessDialog
+            dia = SvnProcessDialog(self.tr('Subversion command'))
+            res = dia.startProcess(args, wd)
+            if res:
+                dia.exec_()
+        
+    def vcsOptionsDialog(self, project, archive, editable=False, parent=None):
+        """
+        Public method to get a dialog to enter repository info.
+        
+        @param project reference to the project object
+        @param archive name of the project in the repository (string)
+        @param editable flag indicating that the project name is editable
+            (boolean)
+        @param parent parent widget (QWidget)
+        @return reference to the instantiated options dialog (SvnOptionsDialog)
+        """
+        from .SvnOptionsDialog import SvnOptionsDialog
+        return SvnOptionsDialog(self, project, parent)
+        
+    def vcsNewProjectOptionsDialog(self, parent=None):
+        """
+        Public method to get a dialog to enter repository info for getting a
+        new project.
+        
+        @param parent parent widget (QWidget)
+        @return reference to the instantiated options dialog
+            (SvnNewProjectOptionsDialog)
+        """
+        from .SvnNewProjectOptionsDialog import SvnNewProjectOptionsDialog
+        return SvnNewProjectOptionsDialog(self, parent)
+        
+    def vcsRepositoryInfos(self, ppath):
+        """
+        Public method to retrieve information about the repository.
+        
+        @param ppath local path to get the repository infos (string)
+        @return string with ready formated info for display (string)
+        """
+        try:
+            entry = self.getClient().info(ppath)
+        except pysvn.ClientError as e:
+            return e.args[0]
+        
+        if hasattr(pysvn, 'svn_api_version'):
+            apiVersion = "{0} {1}".format(
+                ".".join([str(v) for v in pysvn.svn_api_version[:3]]),
+                pysvn.svn_api_version[3])
+        else:
+            apiVersion = QCoreApplication.translate('subversion', "unknown")
+        
+        hmsz = time.strftime("%H:%M:%S %Z", time.localtime(entry.commit_time))
+        if sys.version_info[0] == 2:
+            hmsz = hmsz.decode(sys.getfilesystemencoding())
+        return QCoreApplication.translate(
+            'subversion',
+            """<h3>Repository information</h3>"""
+            """<table>"""
+            """<tr><td><b>PySvn V.</b></td><td>{0}</td></tr>"""
+            """<tr><td><b>Subversion V.</b></td><td>{1}</td></tr>"""
+            """<tr><td><b>Subversion API V.</b></td><td>{2}</td></tr>"""
+            """<tr><td><b>URL</b></td><td>{3}</td></tr>"""
+            """<tr><td><b>Current revision</b></td><td>{4}</td></tr>"""
+            """<tr><td><b>Committed revision</b></td><td>{5}</td></tr>"""
+            """<tr><td><b>Committed date</b></td><td>{6}</td></tr>"""
+            """<tr><td><b>Comitted time</b></td><td>{7}</td></tr>"""
+            """<tr><td><b>Last author</b></td><td>{8}</td></tr>"""
+            """</table>"""
+        )\
+            .format(".".join([str(v) for v in pysvn.version]),
+                    ".".join([str(v) for v in pysvn.svn_version[:3]]),
+                    apiVersion,
+                    entry.url,
+                    entry.revision.number,
+                    entry.commit_revision.number,
+                    time.strftime(
+                        "%Y-%m-%d", time.localtime(entry.commit_time)),
+                    hmsz,
+                    entry.commit_author
+                    )
+    
+    ###########################################################################
+    ## Public Subversion specific methods are below.
+    ###########################################################################
+    
+    def svnGetReposName(self, path):
+        """
+        Public method used to retrieve the URL of the subversion repository
+        path.
+        
+        @param path local path to get the svn repository path for (string)
+        @return string with the repository path URL
+        """
+        client = pysvn.Client()
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        try:
+            entry = client.info(path)
+            url = entry.url
+        except pysvn.ClientError:
+            url = ""
+        locker.unlock()
+        return url
+
+    def svnResolve(self, name):
+        """
+        Public method used to resolve conflicts of a file/directory.
+        
+        @param name file/directory name to be resolved (string)
+        """
+        if isinstance(name, list):
+            dname, fnames = self.splitPathList(name)
+        else:
+            dname, fname = self.splitPath(name)
+            fnames = [fname]
+        
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        cwd = os.getcwd()
+        os.chdir(dname)
+        opts = self.options['global']
+        recurse = "--non-recursive" not in opts
+        client = self.getClient()
+        dlg = SvnDialog(self.tr('Resolving conficts'),
+                        "resolved{0} {1}".format(
+                            (not recurse) and " --non-recursive" or "",
+                            " ".join(fnames)),
+                        client)
+        QApplication.processEvents()
+        try:
+            for name in fnames:
+                client.resolved(name, recurse=recurse)
+        except pysvn.ClientError as e:
+            dlg.showError(e.args[0])
+        locker.unlock()
+        dlg.finish()
+        dlg.exec_()
+        os.chdir(cwd)
+        self.checkVCSStatus()
+    
+    def svnCopy(self, name, project):
+        """
+        Public method used to copy a file/directory.
+        
+        @param name file/directory name to be copied (string)
+        @param project reference to the project object
+        @return flag indicating successfull operation (boolean)
+        """
+        from .SvnCopyDialog import SvnCopyDialog
+        rx_prot = QRegExp('(file:|svn:|svn+ssh:|http:|https:).+')
+        dlg = SvnCopyDialog(name)
+        res = False
+        if dlg.exec_() == QDialog.Accepted:
+            target, force = dlg.getData()
+            
+            client = self.getClient()
+            if rx_prot.exactMatch(target):
+                target = self.__svnURL(target)
+                log = "Copying {0} to {1}".format(name, target)
+            else:
+                log = ""
+                target = target
+            dlg = \
+                SvnDialog(
+                    self.tr('Copying {0}').format(name),
+                    "copy{0} {1} {2}".format(
+                        log and (" --message {0}".format(log)) or "",
+                        name, target),
+                    client, log=log)
+            QApplication.processEvents()
+            locker = QMutexLocker(self.vcsExecutionMutex)
+            try:
+                client.copy(name, target)
+                res = True
+            except pysvn.ClientError as e:
+                res = False
+                dlg.showError(e.args[0])
+            locker.unlock()
+            dlg.finish()
+            dlg.exec_()
+            if res and \
+               not rx_prot.exactMatch(target) and \
+               target.startswith(project.getProjectPath()):
+                if os.path.isdir(name):
+                    project.copyDirectory(name, target)
+                else:
+                    project.appendFile(target)
+        return res
+    
+    def svnListProps(self, name, recursive=False):
+        """
+        Public method used to list the properties of a file/directory.
+        
+        @param name file/directory name (string or list of strings)
+        @param recursive flag indicating a recursive list is requested
+        """
+        if self.propList is None:
+            from .SvnPropListDialog import SvnPropListDialog
+            self.propList = SvnPropListDialog(self)
+        self.propList.show()
+        self.propList.raise_()
+        QApplication.processEvents()
+        self.propList.start(name, recursive)
+        
+    def svnSetProp(self, name, recursive=False):
+        """
+        Public method used to add a property to a file/directory.
+        
+        @param name file/directory name (string or list of strings)
+        @param recursive flag indicating a recursive set is requested
+        """
+        from .SvnPropSetDialog import SvnPropSetDialog
+        dlg = SvnPropSetDialog(recursive)
+        if dlg.exec_() == QDialog.Accepted:
+            propName, propValue, recurse = dlg.getData()
+            if not propName:
+                E5MessageBox.critical(
+                    self.__ui,
+                    self.tr("Subversion Set Property"),
+                    self.tr(
+                        """You have to supply a property name. Aborting."""))
+                return
+            
+            if isinstance(name, list):
+                dname, fnames = self.splitPathList(name)
+            else:
+                dname, fname = self.splitPath(name)
+                fnames = [fname]
+            
+            locker = QMutexLocker(self.vcsExecutionMutex)
+            cwd = os.getcwd()
+            os.chdir(dname)
+            opts = self.options['global']
+            skipchecks = "--skip-checks" in opts
+            client = self.getClient()
+            dlg = \
+                SvnDialog(
+                    self.tr('Subversion Set Property'),
+                    "propset{0}{1} {2} {3} {4}".format(
+                        recurse and " --recurse" or "",
+                        skipchecks and " --skip-checks" or "",
+                        propName, propValue,
+                        " ".join(fnames)),
+                    client)
+            QApplication.processEvents()
+            try:
+                for name in fnames:
+                    client.propset(propName, propValue, name,
+                                   recurse=recurse, skip_checks=skipchecks)
+            except pysvn.ClientError as e:
+                dlg.showError(e.args[0])
+            locker.unlock()
+            dlg.showMessage(self.tr("Property set."))
+            dlg.finish()
+            dlg.exec_()
+            os.chdir(cwd)
+        
+    def svnDelProp(self, name, recursive=False):
+        """
+        Public method used to delete a property of a file/directory.
+        
+        @param name file/directory name (string or list of strings)
+        @param recursive flag indicating a recursive list is requested
+        """
+        from .SvnPropDelDialog import SvnPropDelDialog
+        dlg = SvnPropDelDialog(recursive)
+        if dlg.exec_() == QDialog.Accepted:
+            propName, recurse = dlg.getData()
+            
+            if not propName:
+                E5MessageBox.critical(
+                    self.__ui,
+                    self.tr("Subversion Delete Property"),
+                    self.tr(
+                        """You have to supply a property name. Aborting."""))
+                return
+            
+            if isinstance(name, list):
+                dname, fnames = self.splitPathList(name)
+            else:
+                dname, fname = self.splitPath(name)
+                fnames = [fname]
+            
+            locker = QMutexLocker(self.vcsExecutionMutex)
+            cwd = os.getcwd()
+            os.chdir(dname)
+            opts = self.options['global']
+            skipchecks = "--skip-checks" in opts
+            client = self.getClient()
+            dlg = \
+                SvnDialog(
+                    self.tr('Subversion Delete Property'),
+                    "propdel{0}{1} {2} {3}".format(
+                        recurse and " --recurse" or "",
+                        skipchecks and " --skip-checks" or "",
+                        propName, " ".join(fnames)),
+                    client)
+            QApplication.processEvents()
+            try:
+                for name in fnames:
+                    client.propdel(propName, name,
+                                   recurse=recurse, skip_checks=skipchecks)
+            except pysvn.ClientError as e:
+                dlg.showError(e.args[0])
+            locker.unlock()
+            dlg.showMessage(self.tr("Property deleted."))
+            dlg.finish()
+            dlg.exec_()
+            os.chdir(cwd)
+        
+    def svnListTagBranch(self, path, tags=True):
+        """
+        Public method used to list the available tags or branches.
+        
+        @param path directory name of the project (string)
+        @param tags flag indicating listing of branches or tags
+                (False = branches, True = tags)
+        """
+        if self.tagbranchList is None:
+            from .SvnTagBranchListDialog import SvnTagBranchListDialog
+            self.tagbranchList = SvnTagBranchListDialog(self)
+        self.tagbranchList.show()
+        self.tagbranchList.raise_()
+        QApplication.processEvents()
+        res = self.tagbranchList.start(path, tags)
+        if res:
+            if tags:
+                self.tagsList = self.tagbranchList.getTagList()
+                if not self.showedTags:
+                    self.allTagsBranchesList = \
+                        self.allTagsBranchesList + self.tagsList
+                    self.showedTags = True
+            elif not tags:
+                self.branchesList = self.tagbranchList.getTagList()
+                if not self.showedBranches:
+                    self.allTagsBranchesList = \
+                        self.allTagsBranchesList + self.branchesList
+                    self.showedBranches = True
+        
+    def svnBlame(self, name):
+        """
+        Public method to show the output of the svn blame command.
+        
+        @param name file name to show the blame for (string)
+        """
+        if self.blame is None:
+            from .SvnBlameDialog import SvnBlameDialog
+            self.blame = SvnBlameDialog(self)
+        self.blame.show()
+        self.blame.raise_()
+        QApplication.processEvents()
+        self.blame.start(name)
+        
+    def svnExtendedDiff(self, name):
+        """
+        Public method used to view the difference of a file/directory to the
+        Subversion repository.
+        
+        If name is a directory and is the project directory, all project files
+        are saved first. If name is a file (or list of files), which is/are
+        being edited and has unsaved modification, they can be saved or the
+        operation may be aborted.
+        
+        This method gives the chance to enter the revisions to be compared.
+        
+        @param name file/directory name to be diffed (string)
+        """
+        if isinstance(name, list):
+            names = name[:]
+        else:
+            names = [name]
+        for nam in names:
+            if os.path.isfile(nam):
+                editor = e5App().getObject("ViewManager").getOpenEditor(nam)
+                if editor and not editor.checkDirty():
+                    return
+            else:
+                project = e5App().getObject("Project")
+                if nam == project.ppath and not project.saveAllScripts():
+                    return
+        from .SvnRevisionSelectionDialog import SvnRevisionSelectionDialog
+        dlg = SvnRevisionSelectionDialog()
+        if dlg.exec_() == QDialog.Accepted:
+            revisions = dlg.getRevisions()
+            if self.diff is None:
+                from .SvnDiffDialog import SvnDiffDialog
+                self.diff = SvnDiffDialog(self)
+            self.diff.show()
+            self.diff.raise_()
+            QApplication.processEvents()
+            self.diff.start(name, revisions)
+        
+    def svnUrlDiff(self, name):
+        """
+        Public method used to view the difference of a file/directory of two
+        repository URLs.
+        
+        If name is a directory and is the project directory, all project files
+        are saved first. If name is a file (or list of files), which is/are
+        being edited and has unsaved modification, they can be saved or the
+        operation may be aborted.
+        
+        This method gives the chance to enter the revisions to be compared.
+        
+        @param name file/directory name to be diffed (string)
+        """
+        if isinstance(name, list):
+            names = name[:]
+        else:
+            names = [name]
+        for nam in names:
+            if os.path.isfile(nam):
+                editor = e5App().getObject("ViewManager").getOpenEditor(nam)
+                if editor and not editor.checkDirty():
+                    return
+            else:
+                project = e5App().getObject("Project")
+                if nam == project.ppath and not project.saveAllScripts():
+                    return
+        
+        dname = self.splitPath(names[0])[0]
+        
+        from .SvnUrlSelectionDialog import SvnUrlSelectionDialog
+        dlg = SvnUrlSelectionDialog(self, self.tagsList, self.branchesList,
+                                    dname)
+        if dlg.exec_() == QDialog.Accepted:
+            urls, summary = dlg.getURLs()
+            if self.diff is None:
+                from .SvnDiffDialog import SvnDiffDialog
+                self.diff = SvnDiffDialog(self)
+            self.diff.show()
+            self.diff.raise_()
+            QApplication.processEvents()
+            self.diff.start(name, urls=urls, summary=summary)
+        
+    def __svnGetFileForRevision(self, name, rev=""):
+        """
+        Private method to get a file for a specific revision from the
+        repository.
+        
+        @param name file name to get from the repository (string)
+        @keyparam rev revision to retrieve (integer or string)
+        @return contents of the file (string) and an error message (string)
+        """
+        output = ""
+        error = ""
+        
+        client = self.getClient()
+        try:
+            if rev:
+                if isinstance(rev, int) or rev.isdecimal():
+                    rev = pysvn.Revision(
+                        pysvn.opt_revision_kind.number, int(rev))
+                elif rev.startswith("{"):
+                    dateStr = rev[1:-1]
+                    secs = QDateTime.fromString(dateStr, Qt.ISODate).toTime_t()
+                    rev = pysvn.Revision(pysvn.opt_revision_kind.date, secs)
+                elif rev == "HEAD":
+                    rev = pysvn.Revision(pysvn.opt_revision_kind.head)
+                elif rev == "COMMITTED":
+                    rev = pysvn.Revision(pysvn.opt_revision_kind.committed)
+                elif rev == "BASE":
+                    rev = pysvn.Revision(pysvn.opt_revision_kind.base)
+                elif rev == "WORKING":
+                    rev = pysvn.Revision(pysvn.opt_revision_kind.working)
+                elif rev == "PREV":
+                    rev = pysvn.Revision(pysvn.opt_revision_kind.previous)
+                else:
+                    rev = pysvn.Revision(pysvn.opt_revision_kind.unspecified)
+                output = client.cat(name, revision=rev)
+            else:
+                output = client.cat(name)
+            output = output.decode('utf-8')
+        except pysvn.ClientError as e:
+            error = str(e)
+        
+        return output, error
+    
+    def svnSbsDiff(self, name, extended=False, revisions=None):
+        """
+        Public method used to view the difference of a file to the Mercurial
+        repository side-by-side.
+        
+        @param name file name to be diffed (string)
+        @keyparam extended flag indicating the extended variant (boolean)
+        @keyparam revisions tuple of two revisions (tuple of strings)
+        @exception ValueError raised to indicate an invalid name parameter type
+        """
+        if isinstance(name, list):
+            raise ValueError("Wrong parameter type")
+        
+        if extended:
+            from .SvnRevisionSelectionDialog import SvnRevisionSelectionDialog
+            dlg = SvnRevisionSelectionDialog()
+            if dlg.exec_() == QDialog.Accepted:
+                rev1, rev2 = dlg.getRevisions()
+                if rev1 == "WORKING":
+                    rev1 = ""
+                if rev2 == "WORKING":
+                    rev2 = ""
+            else:
+                return
+        elif revisions:
+            rev1, rev2 = revisions[0], revisions[1]
+        else:
+            rev1, rev2 = "", ""
+        
+        output1, error = self.__svnGetFileForRevision(name, rev=rev1)
+        if error:
+            E5MessageBox.critical(
+                self.__ui,
+                self.tr("Subversion Side-by-Side Difference"),
+                error)
+            return
+        name1 = "{0} (rev. {1})".format(name, rev1 and rev1 or ".")
+        
+        if rev2:
+            output2, error = self.__svnGetFileForRevision(name, rev=rev2)
+            if error:
+                E5MessageBox.critical(
+                    self.__ui,
+                    self.tr("Subversion Side-by-Side Difference"),
+                    error)
+                return
+            name2 = "{0} (rev. {1})".format(name, rev2)
+        else:
+            try:
+                f1 = open(name, "r", encoding="utf-8")
+                output2 = f1.read()
+                f1.close()
+                name2 = name
+            except IOError:
+                E5MessageBox.critical(
+                    self.__ui,
+                    self.tr("Subversion Side-by-Side Difference"),
+                    self.tr(
+                        """<p>The file <b>{0}</b> could not be read.</p>""")
+                    .format(name))
+                return
+        
+        if self.sbsDiff is None:
+            from UI.CompareDialog import CompareDialog
+            self.sbsDiff = CompareDialog()
+        self.sbsDiff.show()
+        self.sbsDiff.raise_()
+        self.sbsDiff.compare(output1, output2, name1, name2)
+    
+    def vcsLogBrowser(self, name, isFile=False):
+        """
+        Public method used to browse the log of a file/directory from the
+        Subversion repository.
+        
+        @param name file/directory name to show the log of (string)
+        @param isFile flag indicating log for a file is to be shown (boolean)
+        """
+        if self.logBrowser is None:
+            from .SvnLogBrowserDialog import SvnLogBrowserDialog
+            self.logBrowser = SvnLogBrowserDialog(self)
+        self.logBrowser.show()
+        self.logBrowser.raise_()
+        QApplication.processEvents()
+        self.logBrowser.start(name, isFile=isFile)
+        
+    def svnLock(self, name, stealIt=False, parent=None):
+        """
+        Public method used to lock a file in the Subversion repository.
+        
+        @param name file/directory name to be locked (string or list of
+            strings)
+        @param stealIt flag indicating a forced operation (boolean)
+        @param parent reference to the parent object of the subversion dialog
+            (QWidget)
+        """
+        comment, ok = QInputDialog.getText(
+            None,
+            self.tr("Subversion Lock"),
+            self.tr("Enter lock comment"),
+            QLineEdit.Normal)
+        
+        if not ok:
+            return
+        
+        if isinstance(name, list):
+            dname, fnames = self.splitPathList(name)
+        else:
+            dname, fname = self.splitPath(name)
+            fnames = [fname]
+        
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        cwd = os.getcwd()
+        os.chdir(dname)
+        client = self.getClient()
+        dlg = \
+            SvnDialog(
+                self.tr('Locking in the Subversion repository'),
+                "lock{0}{1} {2}".format(
+                    stealIt and " --force" or "",
+                    comment and (" --message {0}".format(comment)) or "",
+                    " ".join(fnames)),
+                client, parent=parent)
+        QApplication.processEvents()
+        try:
+            client.lock(fnames, comment, force=stealIt)
+        except pysvn.ClientError as e:
+            dlg.showError(e.args[0])
+        except AttributeError as e:
+            dlg.showError(str(e))
+        locker.unlock()
+        dlg.finish()
+        dlg.exec_()
+        os.chdir(cwd)
+        
+    def svnUnlock(self, name, breakIt=False, parent=None):
+        """
+        Public method used to unlock a file in the Subversion repository.
+        
+        @param name file/directory name to be unlocked (string or list of
+            strings)
+        @param breakIt flag indicating a forced operation (boolean)
+        @param parent reference to the parent object of the subversion dialog
+            (QWidget)
+        """
+        if isinstance(name, list):
+            dname, fnames = self.splitPathList(name)
+        else:
+            dname, fname = self.splitPath(name)
+            fnames = [fname]
+        
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        cwd = os.getcwd()
+        os.chdir(dname)
+        client = self.getClient()
+        dlg = \
+            SvnDialog(
+                self.tr('Unlocking in the Subversion repository'),
+                "unlock{0} {1}".format(
+                    breakIt and " --force" or "",
+                    " ".join(fnames)),
+                client, parent=parent)
+        QApplication.processEvents()
+        try:
+            client.unlock(fnames, force=breakIt)
+        except pysvn.ClientError as e:
+            dlg.showError(e.args[0])
+        except AttributeError as e:
+            dlg.showError(str(e))
+        locker.unlock()
+        dlg.finish()
+        dlg.exec_()
+        os.chdir(cwd)
+        
+    def svnInfo(self, projectPath, name):
+        """
+        Public method to show repository information about a file or directory.
+        
+        @param projectPath path name of the project (string)
+        @param name file/directory name relative to the project (string)
+        """
+        from .SvnInfoDialog import SvnInfoDialog
+        dlg = SvnInfoDialog(self)
+        dlg.start(projectPath, name)
+        dlg.exec_()
+        
+    def svnRelocate(self, projectPath):
+        """
+        Public method to relocate the working copy to a new repository URL.
+        
+        @param projectPath path name of the project (string)
+        """
+        from .SvnRelocateDialog import SvnRelocateDialog
+        currUrl = self.svnGetReposName(projectPath)
+        dlg = SvnRelocateDialog(currUrl)
+        if dlg.exec_() == QDialog.Accepted:
+            newUrl, inside = dlg.getData()
+            if inside:
+                msg = "switch {0} {1}".format(newUrl, projectPath)
+            else:
+                msg = "relocate {0} {1} {2}".format(currUrl, newUrl,
+                                                    projectPath)
+            client = self.getClient()
+            dlg = \
+                SvnDialog(self.tr('Relocating'), msg, client)
+            QApplication.processEvents()
+            locker = QMutexLocker(self.vcsExecutionMutex)
+            try:
+                if inside:
+                    client.switch(projectPath, newUrl)
+                else:
+                    client.relocate(currUrl, newUrl, projectPath, recurse=True)
+            except pysvn.ClientError as e:
+                dlg.showError(e.args[0])
+            locker.unlock()
+            dlg.finish()
+            dlg.exec_()
+        
+    def svnRepoBrowser(self, projectPath=None):
+        """
+        Public method to open the repository browser.
+        
+        @param projectPath path name of the project (string)
+        """
+        if projectPath:
+            url = self.svnGetReposName(projectPath)
+        else:
+            url = None
+        
+        if url is None:
+            url, ok = QInputDialog.getText(
+                None,
+                self.tr("Repository Browser"),
+                self.tr("Enter the repository URL."),
+                QLineEdit.Normal)
+            if not ok or not url:
+                return
+        
+        if self.repoBrowser is None:
+            from .SvnRepoBrowserDialog import SvnRepoBrowserDialog
+            self.repoBrowser = SvnRepoBrowserDialog(self)
+        self.repoBrowser.start(url)
+        self.repoBrowser.show()
+        self.repoBrowser.raise_()
+        
+    def svnRemoveFromChangelist(self, names):
+        """
+        Public method to remove a file or directory from its changelist.
+        
+        Note: Directories will be removed recursively.
+        
+        @param names name or list of names of file or directory to remove
+            (string)
+        """
+        if not isinstance(names, list):
+            names = [names]
+        client = self.getClient()
+        dlg = \
+            SvnDialog(self.tr('Remove from changelist'),
+                      "changelist --remove {0}".format(" ".join(names)),
+                      client)
+        QApplication.processEvents()
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        try:
+            for name in names:
+                client.remove_from_changelists(name)
+        except pysvn.ClientError as e:
+            dlg.showError(e.args[0])
+        locker.unlock()
+        dlg.finish()
+        dlg.exec_()
+        
+    def svnAddToChangelist(self, names):
+        """
+        Public method to add a file or directory to a changelist.
+        
+        Note: Directories will be added recursively.
+        
+        @param names name or list of names of file or directory to add
+            (string)
+        """
+        if not isinstance(names, list):
+            names = [names]
+        
+        clname, ok = QInputDialog.getItem(
+            None,
+            self.tr("Add to changelist"),
+            self.tr("Enter name of the changelist:"),
+            sorted(self.svnGetChangelists()),
+            0, True)
+        if not ok or not clname:
+            return
+
+        client = self.getClient()
+        dlg = \
+            SvnDialog(self.tr('Add to changelist'),
+                      "changelist {0}".format(" ".join(names)),
+                      client)
+        QApplication.processEvents()
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        try:
+            for name in names:
+                client.add_to_changelist(name, clname,
+                                         depth=pysvn.depth.infinity)
+        except pysvn.ClientError as e:
+            dlg.showError(e.args[0])
+        locker.unlock()
+        dlg.finish()
+        dlg.exec_()
+    
+    def svnShowChangelists(self, path):
+        """
+        Public method used to inspect the change lists defined for the project.
+        
+        @param path directory name to show change lists for (string)
+        """
+        from .SvnChangeListsDialog import SvnChangeListsDialog
+        self.changeLists = SvnChangeListsDialog(self)
+        self.changeLists.show()
+        QApplication.processEvents()
+        self.changeLists.start(path)
+        
+    def svnGetChangelists(self):
+        """
+        Public method to get a list of all defined change lists.
+        
+        @return list of defined change list names (list of strings)
+        """
+        changelists = []
+        client = self.getClient()
+        if hasattr(client, 'get_changelist'):
+            ppath = e5App().getObject("Project").getProjectPath()
+            locker = QMutexLocker(self.vcsExecutionMutex)
+            try:
+                entries = client.get_changelist(ppath,
+                                                depth=pysvn.depth.infinity)
+                for entry in entries:
+                    changelist = entry[1]
+                    if sys.version_info[0] == 2:
+                        changelist = changelist.decode('utf-8')
+                    if changelist not in changelists:
+                        changelists.append(changelist)
+            except pysvn.ClientError:
+                pass
+            locker.unlock()
+        
+        return changelists
+        
+    def svnUpgrade(self, path):
+        """
+        Public method to upgrade the working copy format.
+        
+        @param path directory name to show change lists for (string)
+        """
+        client = self.getClient()
+        dlg = \
+            SvnDialog(self.tr('Upgrade'),
+                      "upgrade {0}".format(path),
+                      client)
+        QApplication.processEvents()
+        locker = QMutexLocker(self.vcsExecutionMutex)
+        try:
+            client.upgrade(path)
+        except pysvn.ClientError as e:
+            dlg.showError(e.args[0])
+        locker.unlock()
+        dlg.finish()
+        dlg.exec_()
+
+    ###########################################################################
+    ## Private Subversion specific methods are below.
+    ###########################################################################
+    
+    def __svnURL(self, url):
+        """
+        Private method to format a url for subversion.
+        
+        @param url unformatted url string (string)
+        @return properly formated url for subversion (string)
+        """
+        url = self.svnNormalizeURL(url)
+        url = url.split(':', 2)
+        if len(url) == 3:
+            scheme = url[0]
+            host = url[1]
+            port, path = url[2].split("/", 1)
+            return "{0}:{1}:{2}/{3}".format(scheme, host, port,
+                                            Utilities.quote(path))
+        else:
+            scheme = url[0]
+            if scheme == "file":
+                return "{0}:{1}".format(scheme, Utilities.quote(url[1]))
+            else:
+                try:
+                    host, path = url[1][2:].split("/", 1)
+                except ValueError:
+                    host = url[1][2:]
+                    path = ""
+                return "{0}://{1}/{2}".format(scheme, host,
+                                              Utilities.quote(path))
+
+    def svnNormalizeURL(self, url):
+        """
+        Public method to normalize a url for subversion.
+        
+        @param url url string (string)
+        @return properly normalized url for subversion (string)
+        """
+        protocol, url = url.split("://", 1)
+        if url.startswith("\\\\"):
+            url = url[2:]
+        if protocol == "file":
+            url = os.path.normcase(url)
+            if url[1] == ":":
+                url = url.replace(":", "|", 1)
+        url = url.replace('\\', '/')
+        if url.endswith('/'):
+            url = url[:-1]
+        if not url.startswith("/") and url[1] in [":", "|"]:
+            url = "/{0}".format(url)
+        return "{0}://{1}".format(protocol, url)
+
+    ###########################################################################
+    ## Methods to get the helper objects are below.
+    ###########################################################################
+    
+    def vcsGetProjectBrowserHelper(self, browser, project,
+                                   isTranslationsBrowser=False):
+        """
+        Public method to instanciate a helper object for the different
+        project browsers.
+        
+        @param browser reference to the project browser object
+        @param project reference to the project object
+        @param isTranslationsBrowser flag indicating, the helper is requested
+            for the translations browser (this needs some special treatment)
+        @return the project browser helper object
+        """
+        from .ProjectBrowserHelper import SvnProjectBrowserHelper
+        return SvnProjectBrowserHelper(self, browser, project,
+                                       isTranslationsBrowser)
+        
+    def vcsGetProjectHelper(self, project):
+        """
+        Public method to instanciate a helper object for the project.
+        
+        @param project reference to the project object
+        @return the project helper object
+        """
+        helper = self.__plugin.getProjectHelper()
+        helper.setObjects(self, project)
+        self.__wcng = \
+            os.path.exists(
+                os.path.join(project.getProjectPath(), ".svn", "format")) or \
+            os.path.exists(
+                os.path.join(project.getProjectPath(), "_svn", "format")) or \
+            os.path.exists(
+                os.path.join(project.getProjectPath(), ".svn", "wc.db")) or \
+            os.path.exists(
+                os.path.join(project.getProjectPath(), "_svn", "wc.db"))
+        return helper
+
+    ###########################################################################
+    ##  Status Monitor Thread methods
+    ###########################################################################
+
+    def _createStatusMonitorThread(self, interval, project):
+        """
+        Protected method to create an instance of the VCS status monitor
+        thread.
+        
+        @param interval check interval for the monitor thread in seconds
+            (integer)
+        @param project reference to the project object
+        @return reference to the monitor thread (QThread)
+        """
+        from .SvnStatusMonitorThread import SvnStatusMonitorThread
+        return SvnStatusMonitorThread(interval, project, self)

eric ide

mercurial