# -*- coding: utf-8 -*-

# Copyright (c) 2003 - 2006 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the version control systems interface to Subversion.
"""

import os
import shutil
import types
import urllib

from qt import *

from KdeQt import KQMessageBox, KQInputDialog

from VCS.VersionControl import VersionControl
from SvnDialog import SvnDialog
from CommitDialog import SvnCommitDialog
from LogDialog import SvnLogDialog
from DiffDialog import SvnDiffDialog
from RevisionSelectionDialog import SvnRevisionSelectionDialog
from StatusDialog import SvnStatusDialog
from TagDialog import SvnTagDialog
from TagBranchListDialog import SvnTagBranchListDialog
from CopyDialog import SvnCopyDialog
from CommandDialog import SvnCommandDialog
from SwitchDialog import SvnSwitchDialog
from MergeDialog import SvnMergeDialog
from PropListDialog import SvnPropListDialog
from PropSetDialog import SvnPropSetDialog
from OptionsDialog import SvnOptionsDialog
from NewProjectDialog import SvnNewProjectOptionsDialog
from ProjectBrowserHelper import SvnProjectBrowserHelper
from ProjectHelper import SvnProjectHelper
import Utilities

class Subversion(VersionControl):
    """
    Class implementing the version control systems interface to Subversion.
    """
    def __init__(self, parent=None, name=None):
        """
        Constructor
        
        @param parent parent widget (QWidget)
        @param name name of this object (string or QString)
        """
        VersionControl.__init__(self, parent, name)
        self.defaultOptions = {
            'global' : [''],
            'commit' : [''],
            'checkout' : [''],
            'update' : [''],
            'add' : [''],
            'remove' : [''],
            'diff' : [''],
            'log' : [''],
            'history' : [''],
            'status' : [''],
            'tag' : [''],
            'export' : ['']
        }
        self.interestingDataKeys = [
            "standardLayout",
        ]
        
        self.options = self.defaultOptions
        self.otherData["standardLayout"] = 1
        self.tagList = QStringList()
        self.mergeList = [QStringList(), QStringList(), QStringList()]
        
        self.tagTypeList = QStringList()
        self.tagTypeList.append('tags')
        self.tagTypeList.append('branches')
        
        self.commandHistory = QStringList()
        self.wdHistory = QStringList()
        
        if os.environ.has_key("SVN_ASP_DOT_NET_HACK"):
            self.adminDir = '_svn'
        else:
            self.adminDir = '.svn'
        
        # regular expression object for evaluation of the status aoutput
        self.rx_status1 = QRegExp('(.{8})\\s+([0-9-]+)\\s+([0-9?]+)\\s+([\\w?]+)\\s+(.+)')
        self.rx_status2 = QRegExp('(.{8})\\s+(.+)\\s*')
        self.statusCache = {}
        
    def vcsExists(self):
        """
        Public method used to test for the presence of the svn executable.
        
        @return flag indicating the existance (boolean)
        """
        self.versionStr = QString.null
        
##        self.proc = QProcess('svn')
##        self.proc.addArgument('--version')
##        self.connect(self.proc, SIGNAL('processExited()'), self.vcsExistsProcExited)
##        self.connect(self.proc, SIGNAL('readyReadStdout()'), self.vcsExistsReadyReadStdout)
##        return self.proc.start()
        process = QProcess()
        process.addArgument('svn')
        process.addArgument('--version')
        if self.startSynchronizedProcess(process, 30000):
            output = str(process.readStdout())
            self.versionStr = output.split()[2]
            return True
        
        return False
        
    def vcsInit(self, vcsDir, noDialog=0):
        """
        Public method used to initialize the subversion repository.
        
        The subversion repository has to be initialized from outside eric3
        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 1
        
    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:
            KQMessageBox.critical(None,
                self.trUtf8("Create project in repository"),
                self.trUtf8("""The project could not be created in the repository."""
                            """ Maybe the given repository doesn't exist or the"""
                            """ repository server is down."""),
                self.trUtf8("&OK"),
                QString.null,
                QString.null,
                0, -1)
        else:
            cwdIsPpath = 0
            if os.getcwd() == project.ppath:
                os.chdir(os.path.dirname(project.ppath))
                cwdIsPpath = 1
            tmpProjectDir = "%s_tmp" % project.ppath
            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"], 1)
            pfn = project.pfile
            if not os.path.isfile(pfn):
                pfn += "z"
            if not os.path.isfile(pfn):
                KQMessageBox.critical(None,
                    self.trUtf8("New project"),
                    self.trUtf8("""The project could not be checked out of the repository.<br />"""
                                """Restoring the original contents."""),
                    self.trUtf8("&OK"),
                    QString.null,
                    QString.null,
                    0, -1)
                if os.getcwd() == project.ppath:
                    os.chdir(os.path.dirname(project.ppath))
                    cwdIsPpath = 1
                else:
                    cwdIsPpath = 0
                shutil.rmtree(project.ppath, 1)
                os.rename(tmpProjectDir, project.ppath)
                project.pdata["VCS"] = ['None']
                project.vcs = None
                project.setDirty(1)
                project.saveProject()
                return
            shutil.rmtree(tmpProjectDir, 1)
            project.openProject(pfn)
        
    def vcsImport(self, vcsDataDict, projectDir, noDialog=0):
        """
        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)
        """
        noDialog = 0
        msg = QString(vcsDataDict["message"])
        if msg.isEmpty():
            msg = QString('***')
        
        vcsDir = self.svnNormalizeURL(vcsDataDict["url"])
        if vcsDir.startswith('/') or vcsDir[1] == '|':
            vcsDir = 'file://%s' % vcsDir
            
        project = vcsDir[vcsDir.rfind('/')+1:]
        
        # create the dir structure to be imported into the repository
        tmpDir = '%s_tmp' % 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, e:
            if os.path.isdir(tmpDir):
                shutil.rmtree(tmpDir, 1)            
            return 0, 0
        
        proc = QProcess('svn')
        proc.addArgument('import')
        self.addArguments(proc, self.options['global'])
        proc.addArgument('-m')
        proc.addArgument(msg)
        proc.addArgument(self.svnURL(vcsDir))
        proc.setWorkingDirectory(QDir(os.path.join(tmpDir, project)))
        
        if noDialog:
            status = self.startSynchronizedProcess(proc)
        else:
            dia = SvnDialog(self.trUtf8('Importing project into Subversion repository'))
            res = dia.startProcess(proc)
            if res:
                dia.exec_loop()
            status = dia.normalExit()
            
        shutil.rmtree(tmpDir, 1)
        return status, 0
        
    def vcsCheckout(self, vcsDataDict, projectDir, noDialog=0):
        """
        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 = 0
        try:
            tag = vcsDataDict["tag"]
        except KeyError:
            tag = None
        vcsDir = self.svnNormalizeURL(vcsDataDict["url"])
        if vcsDir.startswith('/') or vcsDir[1] == '|':
            vcsDir = 'file://%s' % vcsDir
            
        if self.otherData["standardLayout"]:
            if tag is None or tag == '':
                svnUrl = '%s/trunk' % vcsDir
            else:
                if not tag.startswith('tags') and not tag.startswith('branches'):
                    type, ok = KQInputDialog.getItem(\
                        self.trUtf8("Subversion Checkout"),
                        self.trUtf8("The tag must be a normal tag (tags) or a branch tag (branches)."
                            " Please select from the list."),
                        self.tagTypeList,
                        0, 0)
                    if not ok:
                        return 0
                    tag = '%s/%s' % (unicode(type), tag)
                svnUrl = '%s/%s' % (vcsDir, tag)
        else:
            svnUrl = vcsDir
        proc = QProcess('svn')
        proc.addArgument('checkout')
        self.addArguments(proc, self.options['global'])
        self.addArguments(proc, self.options['checkout'])
        proc.addArgument(self.svnURL(svnUrl))
        proc.addArgument(projectDir)
        
        if noDialog:
            return self.startSynchronizedProcess(proc)
        else:
            dia = SvnDialog(self.trUtf8('Checking project out of Subversion repository'))
            res = dia.startProcess(proc)
            if res:
                dia.exec_loop()
            return dia.normalExit()
        
    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://%s' % vcsDir
            
        if self.otherData["standardLayout"]:
            if tag is None or tag == '':
                svnUrl = '%s/trunk' % vcsDir
            else:
                if not tag.startswith('tags') and not tag.startswith('branches'):
                    type, ok = KQInputDialog.getItem(\
                        self.trUtf8("Subversion Export"),
                        self.trUtf8("The tag must be a normal tag (tags) or a branch tag (branches)."
                            " Please select from the list."),
                        self.tagTypeList,
                        0, 0)
                    if not ok:
                        return 0
                    tag = '%s/%s' % (unicode(type), tag)
                svnUrl = '%s/%s' % (vcsDir, tag)
        else:
            svnUrl = vcsDir
        proc = QProcess('svn')
        proc.addArgument('export')
        self.addArguments(proc, self.options['global'])
        proc.addArgument("--force")
        proc.addArgument(self.svnURL(svnUrl))
        proc.addArgument(projectDir)
        
        dia = SvnDialog(self.trUtf8('Exporting project from Subversion repository'))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        return dia.normalExit()
        
    def vcsCommit(self, name, message, noDialog=0, parent=None):
        """
        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
        @param parent reference to the parent object of the commit dialog (QWidget)
        @return flag indicating an execution without errors (boolean)
        """
        msg = QString(message)
        
        if not noDialog and msg.isEmpty():
            # call CommitDialog and get message from there
            dlg = SvnCommitDialog(parent)
            if dlg.exec_loop() == QDialog.Accepted:
                msg = dlg.logMessage()
            else:
                return 0
                
        if msg.isEmpty():
            msg = QString('***')
            
        proc = QProcess('svn')
        proc.addArgument('commit')
        self.addArguments(proc, self.options['global'])
        self.addArguments(proc, self.options['commit'])
        proc.addArgument('-m')
        proc.addArgument(msg)
        if type(name) is types.ListType:
            dname, fnames = self.splitPathList(name)
            self.addArguments(proc, fnames)
        else:
            dname, fname = self.splitPath(name)
            proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        try:
            if self.svnGetReposName(dname).startswith('http') or \
               self.svnGetReposName(dname).startswith('svn'):
                noDialog = 0
        except:
            noDialog = 0
        
        if noDialog:
            return self.startSynchronizedProcess(proc)
        else:
            dia = SvnDialog(self.trUtf8('Commiting changes to Subversion repository'), parent)
            res = dia.startProcess(proc)
            if res:
                dia.exec_loop()
            return dia.normalExit()
        
    def vcsUpdate(self, name):
        """
        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)
        """
        proc = QProcess('svn')
        proc.addArgument('update')
        self.addArguments(proc, self.options['global'])
        self.addArguments(proc, self.options['update'])
        if type(name) is types.ListType:
            dname, fnames = self.splitPathList(name)
            self.addArguments(proc, fnames)
        else:
            dname, fname = self.splitPath(name)
            proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = SvnDialog(self.trUtf8('Synchronizing with the Subversion repository'))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        
    def vcsAdd(self, name, isDir=0):
        """
        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)
        """
        proc = QProcess('svn')
        proc.addArgument('add')
        self.addArguments(proc, self.options['global'])
        self.addArguments(proc, self.options['add'])
        proc.addArgument('--non-recursive')
        
        if type(name) is types.ListType:
            if isDir:
                dname, fname = os.path.split(unicode(name[0]))
            else:
                dname, fnames = self.splitPathList(name)
        else:
            if isDir:
                dname, fname = os.path.split(unicode(name))
            else:
                dname, fname = self.splitPath(name)
        tree = []
        wdir = dname
        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.split(dname)[0]
            wdir = dname
        self.addArguments(proc, tree)
        
        if type(name) is types.ListType:
            tree2 = []
            for n in name:
                d = os.path.split(n)[0]
                while not os.path.exists(os.path.join(d, self.adminDir)):
                    if d in tree2 + tree:
                        break
                    tree2.append(d)
                    d = os.path.split(d)[0]
            tree2.reverse()
            self.addArguments(proc, tree2)
            self.addArguments(proc, name)
        else:
            proc.addArgument(name)
        proc.setWorkingDirectory(QDir(wdir))
        
        dia = SvnDialog(self.trUtf8('Adding files/directories to the Subversion repository'))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        
    def vcsAddBinary(self, name, isDir=0):
        """
        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))
        """
        proc = QProcess('svn')
        proc.addArgument('add')
        self.addArguments(proc, self.options['global'])
        self.addArguments(proc, self.options['add'])
        
        tree = []
        if type(path) is types.ListType:
            dname, fnames = self.splitPathList(path)
            for n in path:
                d = os.path.split(n)[0]
                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.split(d)[0]
            tree.reverse()
        else:
            dname, fname = os.path.split(unicode(path))
            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, dummy = os.path.split(dname)
        if tree:
            self.vcsAdd(tree, 1)
        
        if type(path) is types.ListType:
            self.addArguments(proc, path)
        else:
            proc.addArgument(path)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = SvnDialog(self.trUtf8('Adding directory trees to the Subversion repository'))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        
    def vcsRemove(self, name, project=0):
        """
        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)
        @return flag indicating successfull operation (boolean)
        """
        proc = QProcess('svn')
        proc.addArgument('delete')
        self.addArguments(proc, self.options['global'])
        self.addArguments(proc, self.options['remove'])
        if type(name) is types.ListType:
            self.addArguments(proc, name)
        else:
            proc.addArgument(name)
        
        dia = SvnDialog(self.trUtf8('Removing files/directories from the Subversion repository'))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
            res = dia.normalExit()
            
        return res
        
    def vcsLog(self, name):
        """
        Public method used to view the log of a file/directory from the Subversion repository.
        
        @param name file/directory name to show the log of (string)
        """
        self.log = SvnLogDialog(self)
        self.log.show()
        self.log.start(name)
        
    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 type(name) is types.ListType:
            names = name[:]
        else:
            names = [name]
        for nam in names:
            if os.path.isfile(nam):
                editor = qApp.mainWidget().getViewManager().getOpenEditor(nam)
                if editor and not editor.checkDirty() :
                    return
            else:
                project = qApp.mainWidget().getProject()
                if nam == project.ppath and not project.saveAllScripts():
                    return
        self.diff = SvnDiffDialog(self)
        self.diff.show()
        self.diff.start(name)
        
    def vcsStatus(self, name):
        """
        Public method used to view the status of a file in the Subversion repository.
        
        @param name file/directory name to show the status of (string or list of strings)
        """
        self.status = SvnStatusDialog(self)
        self.status.show()
        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:
            KQMessageBox.critical(None,
                self.trUtf8("Subversion Error"),
                self.trUtf8("""The URL of the project repository could not be"""
                    """ retrieved from the working copy. The tag operation will"""
                    """ be aborted"""),
                self.trUtf8("OK"),
                QString.null,
                QString.null,
                0,-1)
            return
            
        if self.otherData["standardLayout"]:
            url = None
        else:
            url = self.svnNormalizeURL(reposURL)
        dlg = SvnTagDialog(self.tagList, url, self.otherData["standardLayout"])
        if dlg.exec_loop() == QDialog.Accepted:
            tag, tagOp = dlg.getParameters()
            self.tagList.remove(tag)
            self.tagList.prepend(tag)
        else:
            return
        
        if self.otherData["standardLayout"]:
            rx_base = QRegExp('(.+)/(trunk|tags|branches).*')
            if not rx_base.exactMatch(reposURL):
                KQMessageBox.critical(None,
                    self.trUtf8("Subversion Error"),
                    self.trUtf8("""The URL of the project repository has an"""
                        """ invalid format. The tag operation will"""
                        """ be aborted"""),
                    self.trUtf8("OK"),
                    QString.null,
                    QString.null,
                    0,-1)
                return
            
            reposRoot = unicode(rx_base.cap(1))
            if tagOp in [1, 4]:
                url = '%s/tags/%s' % (reposRoot, urllib.quote(unicode(tag)))
            elif tagOp in [2, 8]:
                url = '%s/branches/%s' % (reposRoot, urllib.quote(unicode(tag)))
        else:
            url = self.svnURL(unicode(tag))
            
        proc = QProcess('svn')
        if tagOp in [1, 2]:
            proc.addArgument('copy')
            self.addArguments(proc, self.options['global'])
            self.addArguments(proc, self.options['tag'])
            proc.addArgument('--message')
            proc.addArgument('Created tag <%s>' % unicode(tag))
            proc.addArgument(reposURL)
            proc.addArgument(url)
        else:
            proc.addArgument('delete')
            self.addArguments(proc, self.options['global'])
            self.addArguments(proc, self.options['tag'])
            proc.addArgument('--message')
            proc.addArgument('Deleted tag <%s>' % unicode(tag))
            proc.addArgument(url)
        
        dia = SvnDialog(self.trUtf8('Tagging %1 in the Subversion repository')
            .arg(name))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        
    def vcsRevert(self, name):
        """
        Public method used to revert changes made to a file/directory.
        
        @param name file/directory name to be reverted (string)
        """
        proc = QProcess('svn')
        proc.addArgument('revert')
        self.addArguments(proc, self.options['global'])
        if type(name) is types.ListType:
            self.addArguments(proc, name)
        else:
            if os.path.isdir(name):
                proc.addArgument('--recursive')
            proc.addArgument(name)
        
        dia = SvnDialog(self.trUtf8('Reverting changes'))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
    
    def vcsSwitch(self, name):
        """
        Public method used to switch a directory to a different tag/branch.
        
        @param name directory name to be switched (string)
        """
        dname, fname = self.splitPath(name)
        
        reposURL = self.svnGetReposName(dname)
        if reposURL is None:
            KQMessageBox.critical(None,
                self.trUtf8("Subversion Error"),
                self.trUtf8("""The URL of the project repository could not be"""
                    """ retrieved from the working copy. The switch operation will"""
                    """ be aborted"""),
                self.trUtf8("OK"),
                QString.null,
                QString.null,
                0,-1)
            return
            
        if self.otherData["standardLayout"]:
            url = None
        else:
            url = self.svnNormalizeURL(reposURL)
        dlg = SvnSwitchDialog(self.tagList, url, self.otherData["standardLayout"])
        if dlg.exec_loop() == QDialog.Accepted:
            tag, tagType = dlg.getParameters()
            self.tagList.remove(tag)
            self.tagList.prepend(tag)
        else:
            return
        
        if self.otherData["standardLayout"]:
            rx_base = QRegExp('(.+)/(trunk|tags|branches).*')
            if not rx_base.exactMatch(reposURL):
                KQMessageBox.critical(None,
                    self.trUtf8("Subversion Error"),
                    self.trUtf8("""The URL of the project repository has an"""
                        """ invalid format. The switch operation will"""
                        """ be aborted"""),
                    self.trUtf8("OK"),
                    QString.null,
                    QString.null,
                    0,-1)
                return
                
            reposRoot = unicode(rx_base.cap(1))
            tn = tag
            if tagType == 1:
                url = '%s/tags/%s' % (reposRoot, urllib.quote(unicode(tag)))
            elif tagType == 2:
                url = '%s/branches/%s' % (reposRoot, urllib.quote(unicode(tag)))
            elif tagType == 4:
                url = '%s/trunk' % (reposRoot)
                tn = QString('HEAD')
        else:
            url = self.svnURL(unicode(tag))
            tn = url
            
        proc = QProcess('svn')
        proc.addArgument('switch')
        proc.addArgument(url)
        proc.addArgument(name)
        
        dia = SvnDialog(self.trUtf8('Switching to %1')
            .arg(tn))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        
    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)
        
        dlg = SvnMergeDialog(self.mergeList[0], self.mergeList[1], self.mergeList[2])
        if dlg.exec_loop() == QDialog.Accepted:
            urlrev1, urlrev2, target = dlg.getParameters()
        else:
            return
            
        # remember URL or revision
        self.mergeList[0].remove(urlrev1)
        self.mergeList[0].prepend(urlrev1)
        self.mergeList[1].remove(urlrev2)
        self.mergeList[1].prepend(urlrev2)

        rx_rev = QRegExp('\\d+|HEAD')
        
        proc = QProcess('svn')
        proc.addArgument('merge')
        self.addArguments(proc, self.options['global'])
        if rx_rev.exactMatch(urlrev1):
            proc.addArgument('-r')
            proc.addArgument(QString('%1:%2').arg(urlrev1).arg(urlrev2))
            if target.isEmpty():
                proc.addArgument(name)
            else:
                proc.addArgument(target)
                
            # remember target
            self.mergeList[2].remove(target)
            self.mergeList[2].prepend(target)
        else:
            proc.addArgument(self.svnURL(urlrev1))
            proc.addArgument(self.svnURL(urlrev2))
        proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = SvnDialog(self.trUtf8('Merging %1')
            .arg(name))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        
    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 or
            0 in order to signal an error
        """
        dname, fname = self.splitPath(name)
        dname = unicode(dname)
        fname = unicode(fname)
        
        if fname == '.':
            if os.path.isdir(os.path.join(dname, self.adminDir)):
                return self.canBeCommitted
            else:
                return self.canBeAdded
        
        name = os.path.normcase(unicode(name))
        states = { name : 0 }
        states = self.vcsAllRegisteredStates(states, dname, 0)
        if states[name] == self.canBeCommitted:
            return self.canBeCommitted
        else:
            return self.canBeAdded
        
    def vcsAllRegisteredStates(self, names, dname, shortcut = 1):
        """
        Public method used to get the registered states of a number of files in the vcs.
        
        @param names dictionary with all filenames to be checked as keys
        @param dname directory to check in (string)
        @return the received dictionary completed with a combination of 
            canBeCommited and canBeAdded or None in order to signal an error
        """
        dname = unicode(dname)
        if not os.path.isdir(os.path.join(dname, self.adminDir)):
            # not under version control -> do nothing
            return names
        
        found = 0
        for name in self.statusCache.keys():
            if os.path.dirname(name) == dname:
                found = 1
                if shortcut:
                    break
                if names.has_key(name):
                    names[name] = self.statusCache[name]
        
        if not found:
            process = QProcess()
            process.addArgument('svn')
            process.addArgument('status')
            process.addArgument('--verbose')
            process.addArgument('--non-interactive')
            process.addArgument(dname)
            if self.startSynchronizedProcess(process, 30000):
                output = str(process.readStdout())
                for line in output.splitlines():
                    if self.rx_status1.exactMatch(line):
                        flags = str(self.rx_status1.cap(1))
                        path = self.rx_status1.cap(5).stripWhiteSpace()
                    elif self.rx_status2.exactMatch(line):
                        flags = str(self.rx_status2.cap(1))
                        path = self.rx_status2.cap(2).stripWhiteSpace()
                    else:
                        continue
                    name = os.path.normcase(unicode(path))
                    if flags[0] not in "?I":
                        if names.has_key(name):
                            names[name] = self.canBeCommitted
                        self.statusCache[name] = self.canBeCommitted
                    else:
                        self.statusCache[name] = self.canBeAdded
        
        return names
        
    def clearStatusCache(self):
        """
        Public method to clear the status cache.
        """
        self.statusCache = {}
        
    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)
        """
        proc = QProcess('svn')
        proc.addArgument('cleanup')
        self.addArguments(proc, self.options['global'])
        proc.addArgument(name)
        
        dia = SvnDialog(self.trUtf8('Cleaning up %1')
            .arg(name))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
    
    def vcsCommandLine(self, name):
        """
        Public method used to execute arbitrary subversion commands.
        
        @param name directory name of the working directory (string)
        """
        dlg = SvnCommandDialog(self.commandHistory, self.wdHistory, name)
        if dlg.exec_loop() == QDialog.Accepted:
            command, wd = dlg.getData()
            commandList = Utilities.parseOptionString(command)
            
            # This moves any previous occurence of these arguments to the head
            # of the list.
            self.commandHistory.remove(command)
            self.commandHistory.prepend(command)
            self.wdHistory.remove(wd)
            self.wdHistory.prepend(wd)
            
            proc = QProcess('svn')
            self.addArguments(proc, commandList)
            if not wd.isEmpty():
                proc.setWorkingDirectory(QDir(wd))
            
            dia = SvnDialog(self.trUtf8('Subversion command'))
            res = dia.startProcess(proc)
            if res:
                dia.exec_loop()
        
    def vcsOptionsDialog(self, project, archive, editable=0, 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 SvnOptionsDialog(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 SvnNewProjectOptionsDialog(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 (QString)
        """
        info = {\
            'committed-rev' : '',
            'committed-date' : '',
            'committed-time' : '',
            'url' : '',
            'last-author' : '',
            'revision' : ''
        }
        
        process = QProcess()
        process.addArgument('svn')
        process.addArgument('info')
        process.addArgument('--non-interactive')
        process.addArgument('--xml')
        process.addArgument(ppath)
        if self.startSynchronizedProcess(process, 30000):
            output = str(process.readStdout())
            entryFound = False
            commitFound = False
            for line in output.splitlines():
                line = line.strip()
                if line.startswith('<entry'):
                    entryFound = True
                elif line.startswith('<commit'):
                    commitFound = True
                elif line.startswith('</commit>'):
                    commitFound = False
                elif line.startswith("revision="):
                    rev = line[line.find('"')+1:line.rfind('"')]
                    if entryFound:
                        info['revision'] = rev
                        entryFound = False
                    elif commitFound:
                        info['committed-rev'] = rev
                elif line.startswith('<url>'):
                    info['url'] = \
                        line.replace('<url>', '').replace('</url>', '')
                elif line.startswith('<author>'):
                    info['last-author'] = \
                        line.replace('<author>', '').replace('</author>', '')
                elif line.startswith('<date>'):
                    value = line.replace('<date>', '').replace('</date>', '')
                    date, time = value.split('T')
                    info['committed-date'] = date
                    info['committed-time'] = "%s%s" % (time.split('.')[0], time[-1])
            
        return QString(qApp.translate('subversion',
            """<h3>Repository information</h3>"""
            """<table>"""
            """<tr><td><b>Subversion V.</b></td><td>%1</td></tr>"""
            """<tr><td><b>URL</b></td><td>%2</td></tr>"""
            """<tr><td><b>Current revision</b></td><td>%3</td></tr>"""
            """<tr><td><b>Committed revision</b></td><td>%4</td></tr>"""
            """<tr><td><b>Committed date</b></td><td>%5</td></tr>"""
            """<tr><td><b>Comitted time</b></td><td>%6</td></tr>"""
            """<tr><td><b>Last author</b></td><td>%7</td></tr>"""
            """</table>"""
            ))\
            .arg(self.versionStr)\
            .arg(info['url'])\
            .arg(info['revision'])\
            .arg(info['committed-rev'])\
            .arg(info['committed-date'])\
            .arg(info['committed-time'])\
            .arg(info['last-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
        """
        process = QProcess()
        process.addArgument('svn')
        process.addArgument('info')
        process.addArgument('--non-interactive')
        process.addArgument('--xml')
        process.addArgument(path)
        if self.startSynchronizedProcess(process, 30000):
            output = str(process.readStdout())
            for line in output.splitlines():
                line = line.strip()
                if line.startswith('<url>'):
                    reposURL = line.replace('<url>', '').replace('</url>', '')
                    return reposURL
        
        return None

    def svnResolve(self, name):
        """
        Public method used to resolve conflicts of a file/directory.
        
        @param name file/directory name to be resolved (string)
        """
        proc = QProcess('svn')
        if self.versionStr < "0.27.0":
            proc.addArgument('resolve')
        else:
            proc.addArgument('resolved')
        self.addArguments(proc, self.options['global'])
        if type(name) is types.ListType:
            self.addArguments(proc, name)
        else:
            if os.path.isdir(name):
                proc.addArgument('--recursive')
            proc.addArgument(name)
        
        dia = SvnDialog(self.trUtf8('Resolving conficts'))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
    
    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)
        """
        rx_prot = QRegExp('(file:|svn:|svn+ssh:|http:|https:).+')
        dlg = SvnCopyDialog(name)
        res = 0
        if dlg.exec_loop() == QDialog.Accepted:
            target = dlg.getData()
            
            proc = QProcess('svn')
            proc.addArgument('copy')
            self.addArguments(proc, self.options['global'])
            if rx_prot.exactMatch(target):
                proc.addArgument('--message')
                proc.addArgument(QString('Copying %1 to %2').arg(name).arg(target))
                target = self.svnURL(target)
            proc.addArgument(name)
            proc.addArgument(target)
            
            dia = SvnDialog(self.trUtf8('Copying %1')
                .arg(name))
            res = dia.startProcess(proc)
            if res:
                dia.exec_loop()
                res = dia.normalExit()
                if res and not rx_prot.exactMatch(target):
                    if os.path.isdir(name):
                        project.copyDirectory(name, target)
                    else:
                        project.appendFile(target)
        return res
    
    def svnMove(self, name, project):
        """
        Public method used to move a file/directory.
        
        @param name file/directory name to be moved (string)
        @param project reference to the project object
        @return flag indicating successfull operation (boolean)
        """
        rx_prot = QRegExp('(file:|svn:|svn+ssh:|http:|https:).+')
        dlg = SvnCopyDialog(name, None, 1)
        res = 0
        if dlg.exec_loop() == QDialog.Accepted:
            target = dlg.getData()
            
            proc = QProcess('svn')
            proc.addArgument('move')
            self.addArguments(proc, self.options['global'])
            if rx_prot.exactMatch(target):
                proc.addArgument('--message')
                proc.addArgument(QString('Moving %1 to %2').arg(name).arg(target))
                target = svnURL(target)
            proc.addArgument(name)
            proc.addArgument(target)
            
            dia = SvnDialog(self.trUtf8('Moving %1')
                .arg(name))
            res = dia.startProcess(proc)
            if res:
                dia.exec_loop()
                res = dia.normalExit()
                if res and not rx_prot.exactMatch(target):
                    if os.path.isdir(name):
                        project.moveDirectory(name, target)
                    else:
                        project.removeFile(name)
                        project.appendFile(target)
                        project.renameMainScript(name, target)
        return res
        
    def svnListProps(self, name, recursive = 0):
        """
        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
        """
        self.propList = SvnPropListDialog(self)
        self.propList.show()
        self.propList.start(name, recursive)
        
    def svnSetProp(self, name, recursive = 0):
        """
        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 list is requested
        """
        dlg = SvnPropSetDialog()
        if dlg.exec_loop() == QDialog.Accepted:
            propName, fileFlag, propValue = dlg.getData()
            if propName.isEmpty():
                KQMessageBox.critical(None,
                    self.trUtf8("Subversion Set Property"),
                    self.trUtf8("""You have to supply a property name. Aborting."""),
                    self.trUtf8("&OK"),
                    QString.null,
                    QString.null,
                    0, -1)
                return
            
            proc = QProcess('svn')
            proc.addArgument('propset')
            self.addArguments(proc, self.options['global'])
            if recursive:
                proc.addArgument('--recursive')
            proc.addArgument(propName)
            if fileFlag:
                proc.addArgument('--file')
            proc.addArgument(propValue)
            if type(name) is types.ListType:
                dname, fnames = self.splitPathList(name)
                self.addArguments(proc, fnames)
            else:
                dname, fname = self.splitPath(name)
                proc.addArgument(fname)
            proc.setWorkingDirectory(QDir(dname))
            
            dia = SvnDialog(self.trUtf8('Subversion Set Property'))
            res = dia.startProcess(proc)
            if res:
                dia.exec_loop()
        
    def svnDelProp(self, name, recursive = 0):
        """
        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
        """
        propName, ok = KQInputDialog.getText(\
            self.trUtf8("Subversion Delete Property"),
            self.trUtf8("Enter property name"),
            QLineEdit.Normal)
        
        if not ok:
            return
            
        if propName.isEmpty():
            KQMessageBox.critical(None,
                self.trUtf8("Subversion Delete Property"),
                self.trUtf8("""You have to supply a property name. Aborting."""),
                self.trUtf8("&OK"),
                QString.null,
                QString.null,
                0, -1)
            return
            
        proc = QProcess('svn')
        proc.addArgument('propdel')
        self.addArguments(proc, self.options['global'])
        if recursive:
            proc.addArgument('--recursive')
        proc.addArgument(propName)
        if type(name) is types.ListType:
            dname, fnames = self.splitPathList(name)
            self.addArguments(proc, fnames)
        else:
            dname, fname = self.splitPath(name)
            proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = SvnDialog(self.trUtf8('Subversion Delete Property'))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
            
    def svnListTagBranch(self, path, tags = 1):
        """
        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
                (0 = branches, 1 = tags)
        """
        self.tagbranchList = SvnTagBranchListDialog(self)
        self.tagbranchList.show()
        self.tagbranchList.start(path, tags)
        
    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. An entry of 
        0 for any specific revision is identical to the current revision (i.e the
        revision as found in the working copy)
        
        @param name file/directory name to be diffed (string)
        """
        if type(name) is types.ListType:
            names = name[:]
        else:
            names = [name]
        for nam in names:
            if os.path.isfile(nam):
                editor = qApp.mainWidget().getViewManager().getOpenEditor(nam)
                if editor and not editor.checkDirty() :
                    return
            else:
                project = qApp.mainWidget().getProject()
                if nam == project.ppath and not project.saveAllScripts():
                    return
        dlg = SvnRevisionSelectionDialog()
        if dlg.exec_loop() == QDialog.Accepted:
            revisions = dlg.getRevisions()
            self.diff = SvnDiffDialog(self)
            self.diff.show()
            self.diff.start(name, revisions)

    def svnLogLimited(self, name):
        """
        Public method used to view the (limited) log of a file/directory from the Subversion repository.
        
        @param name file/directory name to show the log of (string)
        """
        noEntries, ok = KQInputDialog.getInteger(\
            self.trUtf8("Subversion Log"),
            self.trUtf8("Select number of entries to show."),
            100, 1, 999999, 1)
        if ok:
            self.log = SvnLogDialog(self)
            self.log.show()
            self.log.start(name, noEntries)
        
    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)
        """
        proc = QProcess('svn')
        proc.addArgument('lock')
        self.addArguments(proc, self.options['global'])
        if stealIt:
            proc.addArgument('--force')
        if type(name) is types.ListType:
            dname, fnames = self.splitPathList(name)
            self.addArguments(proc, fnames)
        else:
            dname, fname = self.splitPath(name)
            proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = SvnDialog(self.trUtf8('Locking in the Subversion repository'), parent)
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        
    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)
        """
        proc = QProcess('svn')
        proc.addArgument('unlock')
        self.addArguments(proc, self.options['global'])
        if breakIt:
            proc.addArgument('--force')
        if type(name) is types.ListType:
            dname, fnames = self.splitPathList(name)
            self.addArguments(proc, fnames)
        else:
            dname, fname = self.splitPath(name)
            proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = SvnDialog(self.trUtf8('Unlocking in the Subversion repository'), parent)
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()

    ############################################################################
    ## Private Subversion specific methods are below.
    ############################################################################
    
##    def vcsExistsReadyReadStdout(self):
##        """
##        Private slot to handle the readyReadStdout signal. 
##        
##        It reads the output of the process and buffers it.
##        """
##        if self.versionStr is QString.null:
##            output = self.proc.readStdout()
##            try:
##                s = unicode(output)
##            except UnicodeDecodeError:
##                s = str(output)
##            self.versionStr = s
##            self.versionStr = str(self.proc.readStdout())
##        
##    def vcsExistsProcExited(self):
##        """
##        Private slot connected to the processExited signal.
##        """
##        self.proc = None
##        self.versionStr = self.versionStr.split()[2]
##        
    def svnURL(self, url):
        """
        Private method to format a url for subversion.
        
        @param url unformatted url string (string)
        @return properly formated url for subversion
        """
        url = self.svnNormalizeURL(url)
        url = tuple(url.split(':', 2))
        if len(url) == 3:
            scheme = url[0]
            host = url[1]
            port, path = url[2].split("/",1)
            return "%s:%s:%s/%s" % (scheme, host, port, urllib.quote(path))
        else:
            scheme = url[0]
            if scheme == "file":
                return "%s:%s" % (scheme, urllib.quote(url[1]))
            else:
                host, path = url[1][2:].split("/",1)
                return "%s://%s/%s" % (scheme, host, urllib.quote(path))

    def svnNormalizeURL(self, url):
        """
        Private method to normalize a url for subversion.
        
        @param url url string (string)
        @return properly normalized url for subversion
        """
        url = url.replace('\\', '/')
        if url.endswith('/'):
            url = url[:-1]
        urll = url.split('//')
        return "%s//%s" % (urll[0], '/'.join(urll[1:]))

    ############################################################################
    ## Methods to get the helper objects are below.
    ############################################################################
    
    def vcsGetProjectBrowserHelper(self, browser, project, isTranslationsBrowser=0):
        """
        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
        """
        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
        """
        return SvnProjectHelper(self, project)
