"Fossies" - the Fresh Open Source Software Archive

Member "eric6-20.9/eric/eric6/Plugins/VcsPlugins/vcsPySvn/subversion.py" (4 Jul 2020, 99747 Bytes) of package /linux/misc/eric6-20.9.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "subversion.py" see the Fossies "Dox" file reference documentation.

    1 # -*- coding: utf-8 -*-
    2 
    3 # Copyright (c) 2006 - 2020 Detlev Offenbach <detlev@die-offenbachs.de>
    4 #
    5 
    6 """
    7 Module implementing the version control systems interface to Subversion.
    8 """
    9 
   10 
   11 import os
   12 import shutil
   13 import time
   14 from urllib.parse import quote
   15 
   16 from PyQt5.QtCore import (
   17     Qt, QMutexLocker, pyqtSignal, QRegExp, QDateTime, QCoreApplication
   18 )
   19 from PyQt5.QtWidgets import QLineEdit, QDialog, QInputDialog, QApplication
   20 
   21 from E5Gui.E5Application import e5App
   22 from E5Gui import E5MessageBox
   23 
   24 from VCS.VersionControl import VersionControl
   25 
   26 import pysvn
   27 
   28 from .SvnDialog import SvnDialog
   29 from .SvnUtilities import getConfigPath, amendConfig, createDefaultConfig
   30 
   31 import Utilities
   32 
   33 
   34 class Subversion(VersionControl):
   35     """
   36     Class implementing the version control systems interface to Subversion.
   37     
   38     @signal committed() emitted after the commit action has completed
   39     """
   40     committed = pyqtSignal()
   41     
   42     def __init__(self, plugin, parent=None, name=None):
   43         """
   44         Constructor
   45         
   46         @param plugin reference to the plugin object
   47         @param parent parent widget (QWidget)
   48         @param name name of this object (string)
   49         """
   50         VersionControl.__init__(self, parent, name)
   51         self.defaultOptions = {
   52             'global': [''],
   53             'commit': [''],
   54             'checkout': [''],
   55             'update': [''],
   56             'add': [''],
   57             'remove': [''],
   58             'diff': [''],
   59             'log': [''],
   60             'history': [''],
   61             'status': [''],
   62             'tag': [''],
   63             'export': ['']
   64         }
   65         self.interestingDataKeys = [
   66             "standardLayout",
   67         ]
   68         
   69         self.__plugin = plugin
   70         self.__ui = parent
   71         
   72         self.options = self.defaultOptions
   73         self.otherData["standardLayout"] = True
   74         self.tagsList = []
   75         self.branchesList = []
   76         self.allTagsBranchesList = []
   77         self.mergeList = [[], [], []]
   78         self.showedTags = False
   79         self.showedBranches = False
   80         
   81         self.tagTypeList = [
   82             'tags',
   83             'branches'
   84         ]
   85         
   86         self.commandHistory = []
   87         self.wdHistory = []
   88         
   89         if (
   90             pysvn.version >= (1, 4, 3, 0) and
   91             "SVN_ASP_DOT_NET_HACK" in os.environ
   92         ):
   93             self.adminDir = '_svn'
   94         else:
   95             self.adminDir = '.svn'
   96         
   97         self.log = None
   98         self.diff = None
   99         self.sbsDiff = None
  100         self.status = None
  101         self.propList = None
  102         self.tagbranchList = None
  103         self.blame = None
  104         self.repoBrowser = None
  105         self.logBrowser = None
  106         
  107         self.statusCache = {}
  108         
  109         self.__commitData = {}
  110         self.__commitDialog = None
  111         
  112         self.__wcng = True
  113         # assume new generation working copy metadata format
  114     
  115     def getPlugin(self):
  116         """
  117         Public method to get a reference to the plugin object.
  118         
  119         @return reference to the plugin object (VcsPySvnPlugin)
  120         """
  121         return self.__plugin
  122     
  123     def getClient(self):
  124         """
  125         Public method to create and initialize the pysvn client object.
  126         
  127         @return the pysvn client object (pysvn.Client)
  128         """
  129         configDir = ""
  130         authCache = True
  131         for arg in self.options['global']:
  132             if arg.startswith("--config-dir"):
  133                 configDir = arg.split("=", 1)[1]
  134             if arg.startswith("--no-auth-cache"):
  135                 authCache = False
  136         
  137         client = pysvn.Client(configDir)
  138         client.exception_style = 1
  139         client.set_auth_cache(authCache)
  140         
  141         return client
  142     
  143     ###########################################################################
  144     ## Methods of the VCS interface
  145     ###########################################################################
  146     
  147     def vcsShutdown(self):
  148         """
  149         Public method used to shutdown the Subversion interface.
  150         """
  151         if self.log is not None:
  152             self.log.close()
  153         if self.diff is not None:
  154             self.diff.close()
  155         if self.sbsDiff is not None:
  156             self.sbsDiff.close()
  157         if self.status is not None:
  158             self.status.close()
  159         if self.propList is not None:
  160             self.propList.close()
  161         if self.tagbranchList is not None:
  162             self.tagbranchList.close()
  163         if self.blame is not None:
  164             self.blame.close()
  165         if self.repoBrowser is not None:
  166             self.repoBrowser.close()
  167         if self.logBrowser is not None:
  168             self.logBrowser.close()
  169         
  170     def vcsExists(self):
  171         """
  172         Public method used to test for the presence of the svn executable.
  173         
  174         @return flag indicating the existance (boolean) and an error message
  175             (string)
  176         """
  177         self.versionStr = ".".join([str(v) for v in pysvn.svn_version[:-1]])
  178         self.version = pysvn.svn_version[:-1]
  179         return True, ""
  180         
  181     def vcsInit(self, vcsDir, noDialog=False):
  182         """
  183         Public method used to initialize the subversion repository.
  184         
  185         The subversion repository has to be initialized from outside eric6
  186         because the respective command always works locally. Therefore we
  187         always return TRUE without doing anything.
  188         
  189         @param vcsDir name of the VCS directory (string)
  190         @param noDialog flag indicating quiet operations (boolean)
  191         @return always TRUE
  192         """
  193         return True
  194         
  195     def vcsConvertProject(self, vcsDataDict, project, addAll=True):
  196         """
  197         Public method to convert an uncontrolled project to a version
  198         controlled project.
  199         
  200         @param vcsDataDict dictionary of data required for the conversion
  201         @type dict
  202         @param project reference to the project object
  203         @type Project
  204         @param addAll flag indicating to add all files to the repository
  205         @type bool
  206         """
  207         success = self.vcsImport(vcsDataDict, project.ppath, addAll=addAll)[0]
  208         if not success:
  209             E5MessageBox.critical(
  210                 self.__ui,
  211                 self.tr("Create project in repository"),
  212                 self.tr(
  213                     """The project could not be created in the repository."""
  214                     """ Maybe the given repository doesn't exist or the"""
  215                     """ repository server is down."""))
  216         else:
  217             cwdIsPpath = False
  218             if os.getcwd() == project.ppath:
  219                 os.chdir(os.path.dirname(project.ppath))
  220                 cwdIsPpath = True
  221             tmpProjectDir = "{0}_tmp".format(project.ppath)
  222             shutil.rmtree(tmpProjectDir, True)
  223             os.rename(project.ppath, tmpProjectDir)
  224             os.makedirs(project.ppath)
  225             self.vcsCheckout(vcsDataDict, project.ppath)
  226             if cwdIsPpath:
  227                 os.chdir(project.ppath)
  228             self.vcsCommit(project.ppath, vcsDataDict["message"], True)
  229             pfn = project.pfile
  230             if not os.path.isfile(pfn):
  231                 pfn += "z"
  232             if not os.path.isfile(pfn):
  233                 E5MessageBox.critical(
  234                     self.__ui,
  235                     self.tr("New project"),
  236                     self.tr(
  237                         """The project could not be checked out of the"""
  238                         """ repository.<br />"""
  239                         """Restoring the original contents."""))
  240                 if os.getcwd() == project.ppath:
  241                     os.chdir(os.path.dirname(project.ppath))
  242                     cwdIsPpath = True
  243                 else:
  244                     cwdIsPpath = False
  245                 shutil.rmtree(project.ppath, True)
  246                 os.rename(tmpProjectDir, project.ppath)
  247                 project.pdata["VCS"] = 'None'
  248                 project.vcs = None
  249                 project.setDirty(True)
  250                 project.saveProject()
  251                 project.closeProject()
  252                 return
  253             shutil.rmtree(tmpProjectDir, True)
  254             project.closeProject(noSave=True)
  255             project.openProject(pfn)
  256         
  257     def vcsImport(self, vcsDataDict, projectDir, noDialog=False, addAll=True):
  258         """
  259         Public method used to import the project into the Subversion
  260         repository.
  261         
  262         @param vcsDataDict dictionary of data required for the import
  263         @type dict
  264         @param projectDir project directory (string)
  265         @type str
  266         @param noDialog flag indicating quiet operations
  267         @type bool
  268         @param addAll flag indicating to add all files to the repository
  269         @type bool
  270         @return tuple containing a flag indicating an execution without errors
  271             and a flag indicating the version controll status
  272         @rtype tuple of (bool, bool)
  273         """
  274         noDialog = False
  275         msg = vcsDataDict["message"]
  276         if not msg:
  277             msg = '***'
  278         
  279         vcsDir = self.svnNormalizeURL(vcsDataDict["url"])
  280         if vcsDir.startswith('/'):
  281             vcsDir = 'file://{0}'.format(vcsDir)
  282         elif vcsDir[1] in ['|', ':']:
  283             vcsDir = 'file:///{0}'.format(vcsDir)
  284         
  285         project = vcsDir[vcsDir.rfind('/') + 1:]
  286         
  287         # create the dir structure to be imported into the repository
  288         tmpDir = '{0}_tmp'.format(projectDir)
  289         try:
  290             os.makedirs(tmpDir)
  291             if self.otherData["standardLayout"]:
  292                 os.mkdir(os.path.join(tmpDir, project))
  293                 os.mkdir(os.path.join(tmpDir, project, 'branches'))
  294                 os.mkdir(os.path.join(tmpDir, project, 'tags'))
  295                 shutil.copytree(
  296                     projectDir, os.path.join(tmpDir, project, 'trunk'))
  297             else:
  298                 shutil.copytree(projectDir, os.path.join(tmpDir, project))
  299         except OSError:
  300             if os.path.isdir(tmpDir):
  301                 shutil.rmtree(tmpDir, True)
  302             return False, False
  303         
  304         locker = QMutexLocker(self.vcsExecutionMutex)
  305         cwd = os.getcwd()
  306         os.chdir(os.path.join(tmpDir, project))
  307         opts = self.options['global']
  308         recurse = "--non-recursive" not in opts
  309         url = self.__svnURL(vcsDir)
  310         client = self.getClient()
  311         if not noDialog:
  312             dlg = SvnDialog(
  313                 self.tr('Importing project into Subversion repository'),
  314                 "import{0} --message {1} .".format(
  315                     (not recurse) and " --non-recursive" or "", msg),
  316                 client)
  317             QApplication.processEvents()
  318         try:
  319             rev = client.import_(".", url, msg, recurse, ignore=True)
  320             status = True
  321         except pysvn.ClientError as e:
  322             status = False
  323             rev = None
  324             if not noDialog:
  325                 dlg.showError(e.args[0])
  326         locker.unlock()
  327         if not noDialog:
  328             rev and dlg.showMessage(self.tr("Imported revision {0}.\n")
  329                                     .format(rev.number))
  330             dlg.finish()
  331             dlg.exec_()
  332         os.chdir(cwd)
  333         
  334         shutil.rmtree(tmpDir, True)
  335         return status, False
  336         
  337     def vcsCheckout(self, vcsDataDict, projectDir, noDialog=False):
  338         """
  339         Public method used to check the project out of the Subversion
  340         repository.
  341         
  342         @param vcsDataDict dictionary of data required for the checkout
  343         @param projectDir project directory to create (string)
  344         @param noDialog flag indicating quiet operations
  345         @return flag indicating an execution without errors (boolean)
  346         """
  347         noDialog = False
  348         try:
  349             tag = vcsDataDict["tag"]
  350         except KeyError:
  351             tag = None
  352         vcsDir = self.svnNormalizeURL(vcsDataDict["url"])
  353         if vcsDir.startswith('/'):
  354             vcsDir = 'file://{0}'.format(vcsDir)
  355         elif vcsDir[1] in ['|', ':']:
  356             vcsDir = 'file:///{0}'.format(vcsDir)
  357             
  358         if self.otherData["standardLayout"]:
  359             if tag is None or tag == '':
  360                 svnUrl = '{0}/trunk'.format(vcsDir)
  361             else:
  362                 if (
  363                     not tag.startswith('tags') and
  364                     not tag.startswith('branches')
  365                 ):
  366                     type_, ok = QInputDialog.getItem(
  367                         None,
  368                         self.tr("Subversion Checkout"),
  369                         self.tr(
  370                             "The tag must be a normal tag (tags) or"
  371                             " a branch tag (branches)."
  372                             " Please select from the list."),
  373                         self.tagTypeList,
  374                         0, False)
  375                     if not ok:
  376                         return False
  377                     tag = '{0}/{1}'.format(type_, tag)
  378                 svnUrl = '{0}/{1}'.format(vcsDir, tag)
  379         else:
  380             svnUrl = vcsDir
  381         
  382         opts = self.options['global'] + self.options['checkout']
  383         recurse = "--non-recursive" not in opts
  384         url = self.__svnURL(svnUrl)
  385         client = self.getClient()
  386         if not noDialog:
  387             dlg = SvnDialog(
  388                 self.tr('Checking project out of Subversion repository'),
  389                 "checkout{0} {1} {2}".format(
  390                     (not recurse) and " --non-recursive" or "",
  391                     url, projectDir),
  392                 client)
  393             QApplication.processEvents()
  394         locker = QMutexLocker(self.vcsExecutionMutex)
  395         try:
  396             client.checkout(url, projectDir, recurse)
  397             status = True
  398         except pysvn.ClientError as e:
  399             status = False
  400             if not noDialog:
  401                 dlg.showError(e.args[0])
  402         locker.unlock()
  403         if not noDialog:
  404             dlg.finish()
  405             dlg.exec_()
  406         return status
  407         
  408     def vcsExport(self, vcsDataDict, projectDir):
  409         """
  410         Public method used to export a directory from the Subversion
  411         repository.
  412         
  413         @param vcsDataDict dictionary of data required for the checkout
  414         @param projectDir project directory to create (string)
  415         @return flag indicating an execution without errors (boolean)
  416         """
  417         try:
  418             tag = vcsDataDict["tag"]
  419         except KeyError:
  420             tag = None
  421         vcsDir = self.svnNormalizeURL(vcsDataDict["url"])
  422         if vcsDir.startswith('/') or vcsDir[1] == '|':
  423             vcsDir = 'file://{0}'.format(vcsDir)
  424             
  425         if self.otherData["standardLayout"]:
  426             if tag is None or tag == '':
  427                 svnUrl = '{0}/trunk'.format(vcsDir)
  428             else:
  429                 if (
  430                     not tag.startswith('tags') and
  431                     not tag.startswith('branches')
  432                 ):
  433                     type_, ok = QInputDialog.getItem(
  434                         None,
  435                         self.tr("Subversion Export"),
  436                         self.tr(
  437                             "The tag must be a normal tag (tags) or"
  438                             " a branch tag (branches)."
  439                             " Please select from the list."),
  440                         self.tagTypeList,
  441                         0, False)
  442                     if not ok:
  443                         return False
  444                     tag = '{0}/{1}'.format(type_, tag)
  445                 svnUrl = '{0}/{1}'.format(vcsDir, tag)
  446         else:
  447             svnUrl = vcsDir
  448         
  449         opts = self.options['global']
  450         recurse = "--non-recursive" not in opts
  451         url = self.__svnURL(svnUrl)
  452         client = self.getClient()
  453         dlg = SvnDialog(
  454             self.tr('Exporting project from Subversion repository'),
  455             "export --force{0} {1} {2}".format(
  456                 (not recurse) and " --non-recursive" or "",
  457                 url, projectDir),
  458             client)
  459         QApplication.processEvents()
  460         locker = QMutexLocker(self.vcsExecutionMutex)
  461         try:
  462             client.export(url, projectDir, force=True, recurse=recurse)
  463             status = True
  464         except pysvn.ClientError as e:
  465             status = False
  466             dlg.showError(e.args[0])
  467         locker.unlock()
  468         dlg.finish()
  469         dlg.exec_()
  470         return status
  471         
  472     def vcsCommit(self, name, message, noDialog=False):
  473         """
  474         Public method used to make the change of a file/directory permanent
  475         in the Subversion repository.
  476         
  477         @param name file/directory name to be committed (string or
  478         list of strings)
  479         @param message message for this operation (string)
  480         @param noDialog flag indicating quiet operations
  481         """
  482         if not noDialog and not message:
  483             # call CommitDialog and get message from there
  484             if self.__commitDialog is None:
  485                 from .SvnCommitDialog import SvnCommitDialog
  486                 self.__commitDialog = SvnCommitDialog(
  487                     self.svnGetChangelists(), self.__ui)
  488                 self.__commitDialog.accepted.connect(self.__vcsCommit_Step2)
  489             self.__commitDialog.show()
  490             self.__commitDialog.raise_()
  491             self.__commitDialog.activateWindow()
  492         
  493         self.__commitData["name"] = name
  494         self.__commitData["msg"] = message
  495         self.__commitData["noDialog"] = noDialog
  496         
  497         if noDialog:
  498             self.__vcsCommit_Step2()
  499         
  500     def __vcsCommit_Step2(self):
  501         """
  502         Private slot performing the second step of the commit action.
  503         """
  504         name = self.__commitData["name"]
  505         msg = self.__commitData["msg"]
  506         noDialog = self.__commitData["noDialog"]
  507         
  508         if not noDialog:
  509             # check, if there are unsaved changes, that should be committed
  510             if isinstance(name, list):
  511                 nameList = name
  512             else:
  513                 nameList = [name]
  514             ok = True
  515             for nam in nameList:
  516                 # check for commit of the project
  517                 if os.path.isdir(nam):
  518                     project = e5App().getObject("Project")
  519                     if nam == project.getProjectPath():
  520                         ok &= project.checkAllScriptsDirty(
  521                             reportSyntaxErrors=True) and project.checkDirty()
  522                         continue
  523                 elif os.path.isfile(nam):
  524                     editor = e5App().getObject("ViewManager").getOpenEditor(
  525                         nam)
  526                     if editor:
  527                         ok &= editor.checkDirty()
  528                 if not ok:
  529                     break
  530             
  531             if not ok:
  532                 res = E5MessageBox.yesNo(
  533                     self.__ui,
  534                     self.tr("Commit Changes"),
  535                     self.tr(
  536                         """The commit affects files, that have unsaved"""
  537                         """ changes. Shall the commit be continued?"""),
  538                     icon=E5MessageBox.Warning)
  539                 if not res:
  540                     return
  541         
  542         if self.__commitDialog is not None:
  543             msg = self.__commitDialog.logMessage()
  544             if self.__commitDialog.hasChangelists():
  545                 changelists, keepChangelists = (
  546                     self.__commitDialog.changelistsData()
  547                 )
  548             else:
  549                 changelists, keepChangelists = [], False
  550             self.__commitDialog.deleteLater()
  551             self.__commitDialog = None
  552         else:
  553             changelists, keepChangelists = [], False
  554         
  555         if not msg:
  556             msg = '***'
  557         
  558         if isinstance(name, list):
  559             dname, fnames = self.splitPathList(name)
  560         else:
  561             dname, fname = self.splitPath(name)
  562             fnames = [fname]
  563         
  564         if (
  565             self.svnGetReposName(dname).startswith('http') or
  566             self.svnGetReposName(dname).startswith('svn')
  567         ):
  568             noDialog = False
  569         
  570         locker = QMutexLocker(self.vcsExecutionMutex)
  571         cwd = os.getcwd()
  572         os.chdir(dname)
  573         opts = self.options['global'] + self.options['commit']
  574         recurse = "--non-recursive" not in opts
  575         keeplocks = "--keep-locks" in opts
  576         client = self.getClient()
  577         if not noDialog:
  578             dlg = SvnDialog(
  579                 self.tr('Commiting changes to Subversion repository'),
  580                 "commit{0}{1}{2}{3} --message {4} {5}".format(
  581                     (not recurse) and " --non-recursive" or "",
  582                     keeplocks and " --keep-locks" or "",
  583                     keepChangelists and " --keep-changelists" or "",
  584                     changelists and
  585                     " --changelist ".join([""] + changelists) or "",
  586                     msg, " ".join(fnames)),
  587                 client)
  588             QApplication.processEvents()
  589         try:
  590             if changelists:
  591                 rev = client.checkin(fnames, msg,
  592                                      recurse=recurse, keep_locks=keeplocks,
  593                                      keep_changelist=keepChangelists,
  594                                      changelists=changelists)
  595             else:
  596                 rev = client.checkin(fnames, msg,
  597                                      recurse=recurse, keep_locks=keeplocks)
  598         except pysvn.ClientError as e:
  599             rev = None
  600             if not noDialog:
  601                 dlg.showError(e.args[0])
  602         locker.unlock()
  603         if not noDialog:
  604             rev and dlg.showMessage(self.tr("Committed revision {0}.")
  605                                     .format(rev.number))
  606             dlg.finish()
  607             dlg.exec_()
  608         os.chdir(cwd)
  609         self.committed.emit()
  610         self.checkVCSStatus()
  611         
  612     def vcsUpdate(self, name, noDialog=False):
  613         """
  614         Public method used to update a file/directory with the Subversion
  615         repository.
  616         
  617         @param name file/directory name to be updated (string or list of
  618             strings)
  619         @param noDialog flag indicating quiet operations (boolean)
  620         @return flag indicating, that the update contained an add
  621             or delete (boolean)
  622         """
  623         if isinstance(name, list):
  624             dname, fnames = self.splitPathList(name)
  625         else:
  626             dname, fname = self.splitPath(name)
  627             fnames = [fname]
  628         
  629         locker = QMutexLocker(self.vcsExecutionMutex)
  630         cwd = os.getcwd()
  631         os.chdir(dname)
  632         opts = self.options['global'] + self.options['update']
  633         recurse = "--non-recursive" not in opts
  634         client = self.getClient()
  635         if not noDialog:
  636             dlg = SvnDialog(
  637                 self.tr('Synchronizing with the Subversion repository'),
  638                 "update{0} {1}".format(
  639                     (not recurse) and " --non-recursive" or "",
  640                     " ".join(fnames)),
  641                 client)
  642         QApplication.processEvents()
  643         try:
  644             client.update(fnames, recurse)
  645         except pysvn.ClientError as e:
  646             dlg.showError(e.args[0])
  647         locker.unlock()
  648         if not noDialog:
  649             dlg.finish()
  650             dlg.exec_()
  651             res = dlg.hasAddOrDelete()
  652         else:
  653             res = False
  654         os.chdir(cwd)
  655         self.checkVCSStatus()
  656         return res
  657         
  658     def vcsAdd(self, name, isDir=False, noDialog=False):
  659         """
  660         Public method used to add a file/directory to the Subversion
  661         repository.
  662         
  663         @param name file/directory name to be added (string)
  664         @param isDir flag indicating name is a directory (boolean)
  665         @param noDialog flag indicating quiet operations (boolean)
  666         """
  667         if isinstance(name, list):
  668             if isDir:
  669                 dname, fname = os.path.split(name[0])
  670             else:
  671                 dname, fnames = self.splitPathList(name)
  672         else:
  673             if isDir:
  674                 dname, fname = os.path.split(name)
  675             else:
  676                 dname, fname = self.splitPath(name)
  677         names = []
  678         tree = []
  679         wdir = dname
  680         if self.__wcng:
  681             repodir = dname
  682             while not os.path.isdir(os.path.join(repodir, self.adminDir)):
  683                 repodir = os.path.dirname(repodir)
  684                 if os.path.splitdrive(repodir)[1] == os.sep:
  685                     return  # oops, project is not version controlled
  686             while (
  687                 os.path.normcase(dname) != os.path.normcase(repodir) and
  688                 (os.path.normcase(dname) not in self.statusCache or
  689                  self.statusCache[os.path.normcase(dname)] ==
  690                     self.canBeAdded)
  691             ):
  692                 # add directories recursively, if they aren't in the
  693                 # repository already
  694                 tree.insert(-1, dname)
  695                 dname = os.path.dirname(dname)
  696                 wdir = dname
  697         else:
  698             while not os.path.exists(os.path.join(dname, self.adminDir)):
  699                 # add directories recursively, if they aren't in the
  700                 # repository already
  701                 tree.insert(-1, dname)
  702                 dname = os.path.dirname(dname)
  703                 wdir = dname
  704         names.extend(tree)
  705         
  706         if isinstance(name, list):
  707             tree2 = []
  708             for n in name:
  709                 d = os.path.dirname(n)
  710                 if self.__wcng:
  711                     repodir = d
  712                     while not os.path.isdir(
  713                             os.path.join(repodir, self.adminDir)):
  714                         repodir = os.path.dirname(repodir)
  715                         if os.path.splitdrive(repodir)[1] == os.sep:
  716                             return  # oops, project is not version controlled
  717                     while (
  718                         (os.path.normcase(d) != os.path.normcase(repodir)) and
  719                         (d not in tree2 + tree) and
  720                         (os.path.normcase(d) not in self.statusCache or
  721                          self.statusCache[os.path.normcase(d)] ==
  722                             self.canBeAdded)
  723                     ):
  724                         tree2.append(d)
  725                         d = os.path.dirname(d)
  726                 else:
  727                     while not os.path.exists(os.path.join(d, self.adminDir)):
  728                         if d in tree2 + tree:
  729                             break
  730                         tree2.append(d)
  731                         d = os.path.dirname(d)
  732             tree2.reverse()
  733             names.extend(tree2)
  734             names.extend(name)
  735         else:
  736             names.append(name)
  737         
  738         locker = QMutexLocker(self.vcsExecutionMutex)
  739         cwd = os.getcwd()
  740         os.chdir(wdir)
  741         opts = self.options['global'] + self.options['add']
  742         recurse = False
  743         force = "--force" in opts or noDialog
  744         noignore = "--no-ignore" in opts
  745         client = self.getClient()
  746         if not noDialog:
  747             dlg = SvnDialog(
  748                 self.tr('Adding files/directories to the Subversion'
  749                         ' repository'),
  750                 "add --non-recursive{0}{1} {2}".format(
  751                     force and " --force" or "",
  752                     noignore and " --no-ignore" or "",
  753                     " ".join(names)),
  754                 client)
  755             QApplication.processEvents()
  756         try:
  757             client.add(names, recurse=recurse, force=force,
  758                        ignore=not noignore)
  759         except pysvn.ClientError as e:
  760             if not noDialog:
  761                 dlg.showError(e.args[0])
  762         locker.unlock()
  763         if not noDialog:
  764             dlg.finish()
  765             dlg.exec_()
  766         os.chdir(cwd)
  767         
  768     def vcsAddBinary(self, name, isDir=False):
  769         """
  770         Public method used to add a file/directory in binary mode to the
  771         Subversion repository.
  772         
  773         @param name file/directory name to be added (string)
  774         @param isDir flag indicating name is a directory (boolean)
  775         """
  776         self.vcsAdd(name, isDir)
  777         
  778     def vcsAddTree(self, path):
  779         """
  780         Public method to add a directory tree rooted at path to the Subversion
  781         repository.
  782         
  783         @param path root directory of the tree to be added (string or list of
  784             strings))
  785         """
  786         tree = []
  787         if isinstance(path, list):
  788             dname, fnames = self.splitPathList(path)
  789             for n in path:
  790                 d = os.path.dirname(n)
  791                 if self.__wcng:
  792                     repodir = d
  793                     while not os.path.isdir(
  794                             os.path.join(repodir, self.adminDir)):
  795                         repodir = os.path.dirname(repodir)
  796                         if os.path.splitdrive(repodir)[1] == os.sep:
  797                             return  # oops, project is not version controlled
  798                     while (
  799                         (os.path.normcase(d) != os.path.normcase(repodir)) and
  800                         (d not in tree) and
  801                         (os.path.normcase(d) not in self.statusCache or
  802                          self.statusCache[os.path.normcase(d)] ==
  803                             self.canBeAdded)
  804                     ):
  805                         tree.append(d)
  806                         d = os.path.dirname(d)
  807                 else:
  808                     while not os.path.exists(os.path.join(d, self.adminDir)):
  809                         # add directories recursively,
  810                         # if they aren't in the repository already
  811                         if d in tree:
  812                             break
  813                         tree.append(d)
  814                         d = os.path.dirname(d)
  815             tree.reverse()
  816         else:
  817             dname, fname = os.path.split(path)
  818             if self.__wcng:
  819                 repodir = dname
  820                 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
  821                     repodir = os.path.dirname(repodir)
  822                     if os.path.splitdrive(repodir)[1] == os.sep:
  823                         return  # oops, project is not version controlled
  824                 while (
  825                     (os.path.normcase(dname) != os.path.normcase(repodir)) and
  826                     (os.path.normcase(dname) not in self.statusCache or
  827                      self.statusCache[os.path.normcase(dname)] ==
  828                         self.canBeAdded)
  829                 ):
  830                     # add directories recursively, if they aren't in the
  831                     # repository already
  832                     tree.insert(-1, dname)
  833                     dname = os.path.dirname(dname)
  834             else:
  835                 while not os.path.exists(os.path.join(dname, self.adminDir)):
  836                     # add directories recursively,
  837                     # if they aren't in the repository already
  838                     tree.insert(-1, dname)
  839                     dname = os.path.dirname(dname)
  840         if tree:
  841             self.vcsAdd(tree, True)
  842         
  843         names = []
  844         if isinstance(path, list):
  845             names.extend(path)
  846         else:
  847             names.append(path)
  848         
  849         locker = QMutexLocker(self.vcsExecutionMutex)
  850         cwd = os.getcwd()
  851         os.chdir(dname)
  852         opts = self.options['global'] + self.options['add']
  853         recurse = True
  854         force = "--force" in opts
  855         ignore = "--ignore" in opts
  856         client = self.getClient()
  857         dlg = SvnDialog(
  858             self.tr('Adding directory trees to the Subversion repository'),
  859             "add{0}{1} {2}".format(
  860                 force and " --force" or "",
  861                 ignore and " --ignore" or "",
  862                 " ".join(names)),
  863             client)
  864         QApplication.processEvents()
  865         try:
  866             client.add(names, recurse=recurse, force=force, ignore=ignore)
  867         except pysvn.ClientError as e:
  868             dlg.showError(e.args[0])
  869         locker.unlock()
  870         dlg.finish()
  871         dlg.exec_()
  872         os.chdir(cwd)
  873         
  874     def vcsRemove(self, name, project=False, noDialog=False):
  875         """
  876         Public method used to remove a file/directory from the Subversion
  877         repository.
  878         
  879         The default operation is to remove the local copy as well.
  880         
  881         @param name file/directory name to be removed (string or list of
  882             strings))
  883         @param project flag indicating deletion of a project tree (boolean)
  884             (not needed)
  885         @param noDialog flag indicating quiet operations
  886         @return flag indicating successfull operation (boolean)
  887         """
  888         if not isinstance(name, list):
  889             name = [name]
  890         opts = self.options['global'] + self.options['remove']
  891         force = "--force" in opts or noDialog
  892         client = self.getClient()
  893         if not noDialog:
  894             dlg = SvnDialog(
  895                 self.tr('Removing files/directories from the Subversion'
  896                         ' repository'),
  897                 "remove{0} {1}".format(
  898                     force and " --force" or "",
  899                     " ".join(name)),
  900                 client)
  901             QApplication.processEvents()
  902         locker = QMutexLocker(self.vcsExecutionMutex)
  903         try:
  904             client.remove(name, force=force)
  905             res = True
  906         except pysvn.ClientError as e:
  907             res = False
  908             if not noDialog:
  909                 dlg.showError(e.args[0])
  910         locker.unlock()
  911         if not noDialog:
  912             dlg.finish()
  913             dlg.exec_()
  914         
  915         return res
  916         
  917     def vcsMove(self, name, project, target=None, noDialog=False):
  918         """
  919         Public method used to move a file/directory.
  920         
  921         @param name file/directory name to be moved (string)
  922         @param project reference to the project object
  923         @param target new name of the file/directory (string)
  924         @param noDialog flag indicating quiet operations
  925         @return flag indicating successfull operation (boolean)
  926         """
  927         rx_prot = QRegExp('(file:|svn:|svn+ssh:|http:|https:).+')
  928         opts = self.options['global']
  929         res = False
  930         
  931         if noDialog:
  932             if target is None:
  933                 return False
  934             force = True
  935             accepted = True
  936         else:
  937             from .SvnCopyDialog import SvnCopyDialog
  938             dlg = SvnCopyDialog(name, None, True, "--force" in opts)
  939             accepted = (dlg.exec_() == QDialog.Accepted)
  940             if accepted:
  941                 target, force = dlg.getData()
  942             if not target:
  943                 return False
  944         
  945         if not rx_prot.exactMatch(target):
  946             isDir = os.path.isdir(name)
  947         else:
  948             isDir = False
  949         
  950         if accepted:
  951             client = self.getClient()
  952             if rx_prot.exactMatch(target):
  953                 target = self.__svnURL(target)
  954                 log = "Moving {0} to {1}".format(name, target)
  955             else:
  956                 log = ""
  957                 target = target
  958             if not noDialog:
  959                 dlg = SvnDialog(
  960                     self.tr('Moving {0}').format(name),
  961                     "move{0}{1} {2} {3}".format(
  962                         force and " --force" or "",
  963                         log and (" --message {0}".format(log)) or "",
  964                         name, target),
  965                     client, log=log)
  966                 QApplication.processEvents()
  967             locker = QMutexLocker(self.vcsExecutionMutex)
  968             try:
  969                 client.move(name, target, force=force)
  970                 res = True
  971             except pysvn.ClientError as e:
  972                 res = False
  973                 if not noDialog:
  974                     dlg.showError(e.args[0])
  975             locker.unlock()
  976             if not noDialog:
  977                 dlg.finish()
  978                 dlg.exec_()
  979             if res and not rx_prot.exactMatch(target):
  980                 if target.startswith(project.getProjectPath()):
  981                     if isDir:
  982                         project.moveDirectory(name, target)
  983                     else:
  984                         project.renameFileInPdata(name, target)
  985                 else:
  986                     if isDir:
  987                         project.removeDirectory(name)
  988                     else:
  989                         project.removeFile(name)
  990         return res
  991         
  992     def vcsDiff(self, name):
  993         """
  994         Public method used to view the difference of a file/directory to the
  995         Subversion repository.
  996         
  997         If name is a directory and is the project directory, all project files
  998         are saved first. If name is a file (or list of files), which is/are
  999         being edited and has unsaved modification, they can be saved or the
 1000         operation may be aborted.
 1001         
 1002         @param name file/directory name to be diffed (string)
 1003         """
 1004         if isinstance(name, list):
 1005             names = name[:]
 1006         else:
 1007             names = [name]
 1008         for nam in names:
 1009             if os.path.isfile(nam):
 1010                 editor = e5App().getObject("ViewManager").getOpenEditor(nam)
 1011                 if editor and not editor.checkDirty():
 1012                     return
 1013             else:
 1014                 project = e5App().getObject("Project")
 1015                 if nam == project.ppath and not project.saveAllScripts():
 1016                     return
 1017         if self.diff is None:
 1018             from .SvnDiffDialog import SvnDiffDialog
 1019             self.diff = SvnDiffDialog(self)
 1020         self.diff.show()
 1021         self.diff.raise_()
 1022         QApplication.processEvents()
 1023         self.diff.start(name, refreshable=True)
 1024         
 1025     def vcsStatus(self, name):
 1026         """
 1027         Public method used to view the status of files/directories in the
 1028         Subversion repository.
 1029         
 1030         @param name file/directory name(s) to show the status of
 1031             (string or list of strings)
 1032         """
 1033         if self.status is None:
 1034             from .SvnStatusDialog import SvnStatusDialog
 1035             self.status = SvnStatusDialog(self)
 1036         self.status.show()
 1037         self.status.raise_()
 1038         QApplication.processEvents()
 1039         self.status.start(name)
 1040         
 1041     def vcsTag(self, name):
 1042         """
 1043         Public method used to set the tag of a file/directory in the
 1044         Subversion repository.
 1045         
 1046         @param name file/directory name to be tagged (string)
 1047         """
 1048         dname, fname = self.splitPath(name)
 1049         
 1050         reposURL = self.svnGetReposName(dname)
 1051         if reposURL is None:
 1052             E5MessageBox.critical(
 1053                 self.__ui,
 1054                 self.tr("Subversion Error"),
 1055                 self.tr(
 1056                     """The URL of the project repository could not be"""
 1057                     """ retrieved from the working copy. The tag operation"""
 1058                     """ will be aborted"""))
 1059             return
 1060         
 1061         if self.otherData["standardLayout"]:
 1062             url = None
 1063         else:
 1064             url = self.svnNormalizeURL(reposURL)
 1065         from .SvnTagDialog import SvnTagDialog
 1066         dlg = SvnTagDialog(self.allTagsBranchesList, url,
 1067                            self.otherData["standardLayout"])
 1068         if dlg.exec_() == QDialog.Accepted:
 1069             tag, tagOp = dlg.getParameters()
 1070             if tag in self.allTagsBranchesList:
 1071                 self.allTagsBranchesList.remove(tag)
 1072             self.allTagsBranchesList.insert(0, tag)
 1073         else:
 1074             return
 1075         
 1076         if self.otherData["standardLayout"]:
 1077             rx_base = QRegExp('(.+)/(trunk|tags|branches).*')
 1078             if not rx_base.exactMatch(reposURL):
 1079                 E5MessageBox.critical(
 1080                     self.__ui,
 1081                     self.tr("Subversion Error"),
 1082                     self.tr(
 1083                         """The URL of the project repository has an"""
 1084                         """ invalid format. The tag operation will"""
 1085                         """ be aborted"""))
 1086                 return
 1087             
 1088             reposRoot = rx_base.cap(1)
 1089             if tagOp in [1, 4]:
 1090                 url = '{0}/tags/{1}'.format(reposRoot, quote(tag))
 1091             elif tagOp in [2, 8]:
 1092                 url = '{0}/branches/{1}'.format(reposRoot, quote(tag))
 1093         else:
 1094             url = self.__svnURL(tag)
 1095         
 1096         self.tagName = tag
 1097         client = self.getClient()
 1098         rev = None
 1099         if tagOp in [1, 2]:
 1100             log = 'Created tag <{0}>'.format(self.tagName)
 1101             dlg = SvnDialog(
 1102                 self.tr('Tagging {0} in the Subversion repository')
 1103                     .format(name),
 1104                 "copy --message {0} {1} {2}".format(log, reposURL, url),
 1105                 client, log=log)
 1106             QApplication.processEvents()
 1107             locker = QMutexLocker(self.vcsExecutionMutex)
 1108             try:
 1109                 rev = client.copy(reposURL, url)
 1110             except pysvn.ClientError as e:
 1111                 dlg.showError(e.args[0])
 1112             locker.unlock()
 1113         else:
 1114             log = 'Deleted tag <{0}>'.format(self.tagName)
 1115             dlg = SvnDialog(
 1116                 self.tr('Tagging {0} in the Subversion repository')
 1117                     .format(name),
 1118                 "remove --message {0} {1}".format(log, url),
 1119                 client, log=log)
 1120             QApplication.processEvents()
 1121             locker = QMutexLocker(self.vcsExecutionMutex)
 1122             try:
 1123                 rev = client.remove(url)
 1124             except pysvn.ClientError as e:
 1125                 dlg.showError(e.args[0])
 1126             locker.unlock()
 1127         rev and dlg.showMessage(
 1128             self.tr("Revision {0}.\n").format(rev.number))
 1129         dlg.finish()
 1130         dlg.exec_()
 1131         
 1132     def vcsRevert(self, name):
 1133         """
 1134         Public method used to revert changes made to a file/directory.
 1135         
 1136         @param name file/directory name to be reverted (string)
 1137         """
 1138         recurse = False
 1139         if not isinstance(name, list):
 1140             name = [name]
 1141             if os.path.isdir(name[0]):
 1142                 recurse = True
 1143         
 1144         project = e5App().getObject("Project")
 1145         names = [project.getRelativePath(nam) for nam in name]
 1146         if names[0]:
 1147             from UI.DeleteFilesConfirmationDialog import (
 1148                 DeleteFilesConfirmationDialog
 1149             )
 1150             dia = DeleteFilesConfirmationDialog(
 1151                 self.parent(),
 1152                 self.tr("Revert changes"),
 1153                 self.tr(
 1154                     "Do you really want to revert all changes to these files"
 1155                     " or directories?"),
 1156                 name)
 1157             yes = dia.exec_() == QDialog.Accepted
 1158         else:
 1159             yes = E5MessageBox.yesNo(
 1160                 None,
 1161                 self.tr("Revert changes"),
 1162                 self.tr("""Do you really want to revert all changes of"""
 1163                         """ the project?"""))
 1164         if yes:
 1165             client = self.getClient()
 1166             dlg = SvnDialog(
 1167                 self.tr('Reverting changes'),
 1168                 "revert {0} {1}".format(
 1169                     (not recurse) and " --non-recursive" or "",
 1170                     " ".join(name)),
 1171                 client)
 1172             QApplication.processEvents()
 1173             locker = QMutexLocker(self.vcsExecutionMutex)
 1174             try:
 1175                 client.revert(name, recurse)
 1176             except pysvn.ClientError as e:
 1177                 dlg.showError(e.args[0])
 1178             locker.unlock()
 1179             dlg.finish()
 1180             dlg.exec_()
 1181             self.checkVCSStatus()
 1182     
 1183     def vcsSwitch(self, name):
 1184         """
 1185         Public method used to switch a directory to a different tag/branch.
 1186         
 1187         @param name directory name to be switched (string)
 1188         @return flag indicating, that the switch contained an add
 1189             or delete (boolean)
 1190         """
 1191         dname, fname = self.splitPath(name)
 1192         
 1193         reposURL = self.svnGetReposName(dname)
 1194         if reposURL is None:
 1195             E5MessageBox.critical(
 1196                 self.__ui,
 1197                 self.tr("Subversion Error"),
 1198                 self.tr(
 1199                     """The URL of the project repository could not be"""
 1200                     """ retrieved from the working copy. The switch"""
 1201                     """ operation will be aborted"""))
 1202             return False
 1203         
 1204         if self.otherData["standardLayout"]:
 1205             url = None
 1206         else:
 1207             url = self.svnNormalizeURL(reposURL)
 1208         from .SvnSwitchDialog import SvnSwitchDialog
 1209         dlg = SvnSwitchDialog(self.allTagsBranchesList, url,
 1210                               self.otherData["standardLayout"])
 1211         if dlg.exec_() == QDialog.Accepted:
 1212             tag, tagType = dlg.getParameters()
 1213             if tag in self.allTagsBranchesList:
 1214                 self.allTagsBranchesList.remove(tag)
 1215             self.allTagsBranchesList.insert(0, tag)
 1216         else:
 1217             return False
 1218         
 1219         if self.otherData["standardLayout"]:
 1220             rx_base = QRegExp('(.+)/(trunk|tags|branches).*')
 1221             if not rx_base.exactMatch(reposURL):
 1222                 E5MessageBox.critical(
 1223                     self.__ui,
 1224                     self.tr("Subversion Error"),
 1225                     self.tr(
 1226                         """The URL of the project repository has an"""
 1227                         """ invalid format. The switch operation will"""
 1228                         """ be aborted"""))
 1229                 return False
 1230             
 1231             reposRoot = rx_base.cap(1)
 1232             tn = tag
 1233             if tagType == 1:
 1234                 url = '{0}/tags/{1}'.format(reposRoot, quote(tag))
 1235             elif tagType == 2:
 1236                 url = '{0}/branches/{1}'.format(reposRoot, quote(tag))
 1237             elif tagType == 4:
 1238                 url = '{0}/trunk'.format(reposRoot)
 1239                 tn = 'HEAD'
 1240         else:
 1241             url = self.__svnURL(tag)
 1242             tn = url
 1243         
 1244         client = self.getClient()
 1245         dlg = SvnDialog(self.tr('Switching to {0}').format(tn),
 1246                         "switch {0} {1}".format(url, name),
 1247                         client)
 1248         QApplication.processEvents()
 1249         locker = QMutexLocker(self.vcsExecutionMutex)
 1250         try:
 1251             rev = client.switch(name, url)
 1252             dlg.showMessage(self.tr("Revision {0}.\n").format(rev.number))
 1253         except pysvn.ClientError as e:
 1254             dlg.showError(e.args[0])
 1255         locker.unlock()
 1256         dlg.finish()
 1257         dlg.exec_()
 1258         res = dlg.hasAddOrDelete()
 1259         self.checkVCSStatus()
 1260         return res
 1261         
 1262     def vcsMerge(self, name):
 1263         """
 1264         Public method used to merge a URL/revision into the local project.
 1265         
 1266         @param name file/directory name to be merged (string)
 1267         """
 1268         dname, fname = self.splitPath(name)
 1269         
 1270         opts = self.options['global']
 1271         from .SvnMergeDialog import SvnMergeDialog
 1272         dlg = SvnMergeDialog(self.mergeList[0], self.mergeList[1],
 1273                              self.mergeList[2], "--force" in opts)
 1274         if dlg.exec_() == QDialog.Accepted:
 1275             urlrev1, urlrev2, target, force = dlg.getParameters()
 1276         else:
 1277             return
 1278         
 1279         # remember URL or revision
 1280         if urlrev1 in self.mergeList[0]:
 1281             self.mergeList[0].remove(urlrev1)
 1282         self.mergeList[0].insert(0, urlrev1)
 1283         if urlrev2 in self.mergeList[1]:
 1284             self.mergeList[1].remove(urlrev2)
 1285         self.mergeList[1].insert(0, urlrev2)
 1286         
 1287         rx_rev = QRegExp('\\d+|HEAD|head')
 1288         
 1289         locker = QMutexLocker(self.vcsExecutionMutex)
 1290         cwd = os.getcwd()
 1291         os.chdir(dname)
 1292         recurse = "--non-recursive" not in opts
 1293         if rx_rev.exactMatch(urlrev1):
 1294             if urlrev1 in ["HEAD", "head"]:
 1295                 revision1 = pysvn.Revision(pysvn.opt_revision_kind.head)
 1296                 rev1 = "HEAD"
 1297             else:
 1298                 revision1 = pysvn.Revision(
 1299                     pysvn.opt_revision_kind.number, int(urlrev1))
 1300                 rev1 = urlrev1
 1301             if urlrev2 in ["HEAD", "head"]:
 1302                 revision2 = pysvn.Revision(pysvn.opt_revision_kind.head)
 1303                 rev2 = "HEAD"
 1304             else:
 1305                 revision2 = pysvn.Revision(
 1306                     pysvn.opt_revision_kind.number, int(urlrev2))
 1307                 rev2 = urlrev2
 1308             if not target:
 1309                 url1 = name
 1310                 url2 = name
 1311             else:
 1312                 url1 = target
 1313                 url2 = target
 1314                 
 1315             # remember target
 1316             if target in self.mergeList[2]:
 1317                 self.mergeList[2].remove(target)
 1318             self.mergeList[2].insert(0, target)
 1319         else:
 1320             if "@" in urlrev1:
 1321                 url1, rev = urlrev1.split("@")
 1322                 if rev in ["HEAD", "head"]:
 1323                     revision1 = pysvn.Revision(pysvn.opt_revision_kind.head)
 1324                     rev1 = "HEAD"
 1325                 else:
 1326                     revision1 = pysvn.Revision(
 1327                         pysvn.opt_revision_kind.number, int(rev))
 1328                     rev1 = rev
 1329             else:
 1330                 url1 = urlrev1
 1331                 revision1 = pysvn.Revision(pysvn.opt_revision_kind.unspecified)
 1332                 rev1 = ""
 1333             if "@" in urlrev2:
 1334                 url2, rev = urlrev2.split("@")
 1335                 if rev in ["HEAD", "head"]:
 1336                     revision2 = pysvn.Revision(pysvn.opt_revision_kind.head)
 1337                     rev2 = "HEAD"
 1338                 else:
 1339                     revision2 = pysvn.Revision(
 1340                         pysvn.opt_revision_kind.number, int(rev))
 1341                     rev2 = rev
 1342             else:
 1343                 url2 = urlrev2
 1344                 revision2 = pysvn.Revision(pysvn.opt_revision_kind.unspecified)
 1345                 rev2 = ""
 1346         client = self.getClient()
 1347         dlg = SvnDialog(
 1348             self.tr('Merging {0}').format(name),
 1349             "merge{0}{1} {2} {3} {4}".format(
 1350                 (not recurse) and " --non-recursive" or "",
 1351                 force and " --force" or "",
 1352                 "{0}{1}".format(url1, rev1 and ("@" + rev1) or ""),
 1353                 "{0}{1}".format(url2, rev2 and ("@" + rev2) or ""),
 1354                 fname),
 1355             client)
 1356         QApplication.processEvents()
 1357         try:
 1358             client.merge(url1, revision1, url2, revision2, fname,
 1359                          recurse=recurse, force=force)
 1360         except pysvn.ClientError as e:
 1361             dlg.showError(e.args[0])
 1362         locker.unlock()
 1363         dlg.finish()
 1364         dlg.exec_()
 1365         os.chdir(cwd)
 1366         
 1367     def vcsRegisteredState(self, name):
 1368         """
 1369         Public method used to get the registered state of a file in the vcs.
 1370         
 1371         @param name filename to check (string)
 1372         @return a combination of canBeCommited and canBeAdded
 1373         """
 1374         if self.__wcng:
 1375             return self.__vcsRegisteredState_wcng(name)
 1376         else:
 1377             return self.__vcsRegisteredState_wc(name)
 1378         
 1379     def __vcsRegisteredState_wcng(self, name):
 1380         """
 1381         Private method used to get the registered state of a file in the vcs.
 1382         
 1383         This is the variant for subversion installations using the new
 1384         working copy meta-data format.
 1385         
 1386         @param name filename to check (string)
 1387         @return a combination of canBeCommited and canBeAdded
 1388         """
 1389         if name.endswith(os.sep):
 1390             name = name[:-1]
 1391         name = os.path.normcase(name)
 1392         dname, fname = self.splitPath(name)
 1393         
 1394         if fname == '.' and os.path.isdir(os.path.join(dname, self.adminDir)):
 1395             return self.canBeCommitted
 1396         
 1397         if name in self.statusCache:
 1398             return self.statusCache[name]
 1399         
 1400         name = os.path.normcase(name)
 1401         states = {name: 0}
 1402         states = self.vcsAllRegisteredStates(states, dname, False)
 1403         if states[name] == self.canBeCommitted:
 1404             return self.canBeCommitted
 1405         else:
 1406             return self.canBeAdded
 1407         
 1408     def __vcsRegisteredState_wc(self, name):
 1409         """
 1410         Private method used to get the registered state of a file in the vcs.
 1411         
 1412         This is the variant for subversion installations using the old working
 1413         copy meta-data format.
 1414         
 1415         @param name filename to check (string)
 1416         @return a combination of canBeCommited and canBeAdded
 1417         """
 1418         dname, fname = self.splitPath(name)
 1419         
 1420         if fname == '.':
 1421             if os.path.isdir(os.path.join(dname, self.adminDir)):
 1422                 return self.canBeCommitted
 1423             else:
 1424                 return self.canBeAdded
 1425         
 1426         name = os.path.normcase(name)
 1427         states = {name: 0}
 1428         states = self.vcsAllRegisteredStates(states, dname, False)
 1429         if states[name] == self.canBeCommitted:
 1430             return self.canBeCommitted
 1431         else:
 1432             return self.canBeAdded
 1433         
 1434     def vcsAllRegisteredStates(self, names, dname, shortcut=True):
 1435         """
 1436         Public method used to get the registered states of a number of files
 1437         in the vcs.
 1438         
 1439         <b>Note:</b> If a shortcut is to be taken, the code will only check,
 1440         if the named directory has been scanned already. If so, it is assumed,
 1441         that the states for all files has been populated by the previous run.
 1442         
 1443         @param names dictionary with all filenames to be checked as keys
 1444         @param dname directory to check in (string)
 1445         @param shortcut flag indicating a shortcut should be taken (boolean)
 1446         @return the received dictionary completed with a combination of
 1447             canBeCommited and canBeAdded or None in order to signal an error
 1448         """
 1449         if self.__wcng:
 1450             return self.__vcsAllRegisteredStates_wcng(names, dname, shortcut)
 1451         else:
 1452             return self.__vcsAllRegisteredStates_wc(names, dname, shortcut)
 1453         
 1454     def __vcsAllRegisteredStates_wcng(self, names, dname, shortcut=True):
 1455         """
 1456         Private method used to get the registered states of a number of files
 1457         in the vcs.
 1458         
 1459         This is the variant for subversion installations using the new working
 1460         copy meta-data format.
 1461         
 1462         <b>Note:</b> If a shortcut is to be taken, the code will only check,
 1463         if the named directory has been scanned already. If so, it is assumed,
 1464         that the states for all files has been populated by the previous run.
 1465         
 1466         @param names dictionary with all filenames to be checked as keys
 1467         @param dname directory to check in (string)
 1468         @param shortcut flag indicating a shortcut should be taken (boolean)
 1469         @return the received dictionary completed with a combination of
 1470             canBeCommited and canBeAdded or None in order to signal an error
 1471         """
 1472         if dname.endswith(os.sep):
 1473             dname = dname[:-1]
 1474         dname = os.path.normcase(dname)
 1475         
 1476         found = False
 1477         for name in self.statusCache.keys():
 1478             if name in names:
 1479                 found = True
 1480                 names[name] = self.statusCache[name]
 1481         
 1482         if not found:
 1483             # find the root of the repo
 1484             repodir = dname
 1485             while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 1486                 repodir = os.path.dirname(repodir)
 1487                 if os.path.splitdrive(repodir)[1] == os.sep:
 1488                     return names
 1489             
 1490             from .SvnDialogMixin import SvnDialogMixin
 1491             mixin = SvnDialogMixin()
 1492             client = self.getClient()
 1493             client.callback_get_login = mixin._clientLoginCallback
 1494             client.callback_ssl_server_trust_prompt = (
 1495                 mixin._clientSslServerTrustPromptCallback
 1496             )
 1497             
 1498             try:
 1499                 locker = QMutexLocker(self.vcsExecutionMutex)
 1500                 allFiles = client.status(dname, recurse=True, get_all=True,
 1501                                          ignore=True, update=False)
 1502                 locker.unlock()
 1503                 dirs = [x for x in names.keys() if os.path.isdir(x)]
 1504                 for file in allFiles:
 1505                     name = os.path.normcase(file.path)
 1506                     if self.__isVersioned(file):
 1507                         if name in names:
 1508                             names[name] = self.canBeCommitted
 1509                             dn = name
 1510                             while (
 1511                                 os.path.splitdrive(dn)[1] != os.sep and
 1512                                 dn != repodir
 1513                             ):
 1514                                 dn = os.path.dirname(dn)
 1515                                 if (
 1516                                     dn in self.statusCache and
 1517                                     self.statusCache[dn] ==
 1518                                         self.canBeCommitted
 1519                                 ):
 1520                                     break
 1521                                 self.statusCache[dn] = self.canBeCommitted
 1522                         self.statusCache[name] = self.canBeCommitted
 1523                         if dirs:
 1524                             for d in dirs:
 1525                                 if name.startswith(d):
 1526                                     names[d] = self.canBeCommitted
 1527                                     self.statusCache[d] = self.canBeCommitted
 1528                                     dirs.remove(d)
 1529                                     break
 1530                     else:
 1531                         self.statusCache[name] = self.canBeAdded
 1532             except pysvn.ClientError:
 1533                 locker.unlock()    # ignore pysvn errors
 1534         
 1535         return names
 1536         
 1537     def __vcsAllRegisteredStates_wc(self, names, dname, shortcut=True):
 1538         """
 1539         Private method used to get the registered states of a number of files
 1540         in the VCS.
 1541         
 1542         This is the variant for subversion installations using the old working
 1543         copy meta-data format.
 1544         
 1545         <b>Note:</b> If a shortcut is to be taken, the code will only check,
 1546         if the named directory has been scanned already. If so, it is assumed,
 1547         that the states for all files has been populated by the previous run.
 1548         
 1549         @param names dictionary with all filenames to be checked as keys
 1550         @param dname directory to check in (string)
 1551         @param shortcut flag indicating a shortcut should be taken (boolean)
 1552         @return the received dictionary completed with a combination of
 1553             canBeCommited and canBeAdded or None in order to signal an error
 1554         """
 1555         if not os.path.isdir(os.path.join(dname, self.adminDir)):
 1556             # not under version control -> do nothing
 1557             return names
 1558         
 1559         found = False
 1560         for name in self.statusCache:
 1561             if os.path.dirname(name) == dname:
 1562                 if shortcut:
 1563                     found = True
 1564                     break
 1565                 if name in names:
 1566                     found = True
 1567                     names[name] = self.statusCache[name]
 1568         
 1569         if not found:
 1570             from .SvnDialogMixin import SvnDialogMixin
 1571             mixin = SvnDialogMixin()
 1572             client = self.getClient()
 1573             client.callback_get_login = mixin._clientLoginCallback
 1574             client.callback_ssl_server_trust_prompt = (
 1575                 mixin._clientSslServerTrustPromptCallback
 1576             )
 1577             
 1578             try:
 1579                 locker = QMutexLocker(self.vcsExecutionMutex)
 1580                 allFiles = client.status(dname, recurse=True, get_all=True,
 1581                                          ignore=True, update=False)
 1582                 locker.unlock()
 1583                 for file in allFiles:
 1584                     name = os.path.normcase(file.path)
 1585                     if self.__isVersioned(file):
 1586                         if name in names:
 1587                             names[name] = self.canBeCommitted
 1588                         self.statusCache[name] = self.canBeCommitted
 1589                     else:
 1590                         self.statusCache[name] = self.canBeAdded
 1591             except pysvn.ClientError:
 1592                 locker.unlock()    # ignore pysvn errors
 1593         
 1594         return names
 1595         
 1596     def __isVersioned(self, status):
 1597         """
 1598         Private method to check, if the given status indicates a
 1599         versioned state.
 1600         
 1601         @param status status object to check (pysvn.PysvnStatus)
 1602         @return flag indicating a versioned state (boolean)
 1603         """
 1604         return status["text_status"] in [
 1605             pysvn.wc_status_kind.normal,
 1606             pysvn.wc_status_kind.added,
 1607             pysvn.wc_status_kind.missing,
 1608             pysvn.wc_status_kind.deleted,
 1609             pysvn.wc_status_kind.replaced,
 1610             pysvn.wc_status_kind.modified,
 1611             pysvn.wc_status_kind.merged,
 1612             pysvn.wc_status_kind.conflicted,
 1613         ]
 1614         
 1615     def clearStatusCache(self):
 1616         """
 1617         Public method to clear the status cache.
 1618         """
 1619         self.statusCache = {}
 1620         
 1621     def vcsInitConfig(self, project):
 1622         """
 1623         Public method to initialize the VCS configuration.
 1624         
 1625         This method ensures, that eric specific files and directories are
 1626         ignored.
 1627         
 1628         @param project reference to the project (Project)
 1629         """
 1630         configPath = getConfigPath()
 1631         if os.path.exists(configPath):
 1632             amendConfig()
 1633         else:
 1634             createDefaultConfig()
 1635     
 1636     def vcsName(self):
 1637         """
 1638         Public method returning the name of the vcs.
 1639         
 1640         @return always 'Subversion' (string)
 1641         """
 1642         return "Subversion"
 1643 
 1644     def vcsCleanup(self, name):
 1645         """
 1646         Public method used to cleanup the working copy.
 1647         
 1648         @param name directory name to be cleaned up (string)
 1649         """
 1650         client = self.getClient()
 1651         dlg = SvnDialog(self.tr('Cleaning up {0}').format(name),
 1652                         "cleanup {0}".format(name),
 1653                         client)
 1654         QApplication.processEvents()
 1655         locker = QMutexLocker(self.vcsExecutionMutex)
 1656         try:
 1657             client.cleanup(name)
 1658         except pysvn.ClientError as e:
 1659             dlg.showError(e.args[0])
 1660         locker.unlock()
 1661         dlg.finish()
 1662         dlg.exec_()
 1663     
 1664     def vcsCommandLine(self, name):
 1665         """
 1666         Public method used to execute arbitrary subversion commands.
 1667         
 1668         @param name directory name of the working directory (string)
 1669         """
 1670         from .SvnCommandDialog import SvnCommandDialog
 1671         dlg = SvnCommandDialog(self.commandHistory, self.wdHistory, name)
 1672         if dlg.exec_() == QDialog.Accepted:
 1673             command, wd = dlg.getData()
 1674             commandList = Utilities.parseOptionString(command)
 1675             
 1676             # This moves any previous occurrence of these arguments to the head
 1677             # of the list.
 1678             if command in self.commandHistory:
 1679                 self.commandHistory.remove(command)
 1680             self.commandHistory.insert(0, command)
 1681             if wd in self.wdHistory:
 1682                 self.wdHistory.remove(wd)
 1683             self.wdHistory.insert(0, wd)
 1684             
 1685             args = []
 1686             self.addArguments(args, commandList)
 1687             
 1688             from Plugins.VcsPlugins.vcsSubversion.SvnDialog import (
 1689                 SvnDialog as SvnProcessDialog
 1690             )
 1691             dia = SvnProcessDialog(self.tr('Subversion command'))
 1692             res = dia.startProcess(args, wd)
 1693             if res:
 1694                 dia.exec_()
 1695         
 1696     def vcsOptionsDialog(self, project, archive, editable=False, parent=None):
 1697         """
 1698         Public method to get a dialog to enter repository info.
 1699         
 1700         @param project reference to the project object
 1701         @param archive name of the project in the repository (string)
 1702         @param editable flag indicating that the project name is editable
 1703             (boolean)
 1704         @param parent parent widget (QWidget)
 1705         @return reference to the instantiated options dialog (SvnOptionsDialog)
 1706         """
 1707         from .SvnOptionsDialog import SvnOptionsDialog
 1708         return SvnOptionsDialog(self, project, parent)
 1709         
 1710     def vcsNewProjectOptionsDialog(self, parent=None):
 1711         """
 1712         Public method to get a dialog to enter repository info for getting a
 1713         new project.
 1714         
 1715         @param parent parent widget (QWidget)
 1716         @return reference to the instantiated options dialog
 1717             (SvnNewProjectOptionsDialog)
 1718         """
 1719         from .SvnNewProjectOptionsDialog import SvnNewProjectOptionsDialog
 1720         return SvnNewProjectOptionsDialog(self, parent)
 1721         
 1722     def vcsRepositoryInfos(self, ppath):
 1723         """
 1724         Public method to retrieve information about the repository.
 1725         
 1726         @param ppath local path to get the repository infos (string)
 1727         @return string with ready formated info for display (string)
 1728         """
 1729         try:
 1730             entry = self.getClient().info(ppath)
 1731         except pysvn.ClientError as e:
 1732             return e.args[0]
 1733         
 1734         if hasattr(pysvn, 'svn_api_version'):
 1735             apiVersion = "{0} {1}".format(
 1736                 ".".join([str(v) for v in pysvn.svn_api_version[:3]]),
 1737                 pysvn.svn_api_version[3])
 1738         else:
 1739             apiVersion = QCoreApplication.translate('subversion', "unknown")
 1740         
 1741         hmsz = time.strftime("%H:%M:%S %Z", time.localtime(entry.commit_time))
 1742         return QCoreApplication.translate(
 1743             'subversion',
 1744             """<h3>Repository information</h3>"""
 1745             """<table>"""
 1746             """<tr><td><b>PySvn V.</b></td><td>{0}</td></tr>"""
 1747             """<tr><td><b>Subversion V.</b></td><td>{1}</td></tr>"""
 1748             """<tr><td><b>Subversion API V.</b></td><td>{2}</td></tr>"""
 1749             """<tr><td><b>URL</b></td><td>{3}</td></tr>"""
 1750             """<tr><td><b>Current revision</b></td><td>{4}</td></tr>"""
 1751             """<tr><td><b>Committed revision</b></td><td>{5}</td></tr>"""
 1752             """<tr><td><b>Committed date</b></td><td>{6}</td></tr>"""
 1753             """<tr><td><b>Comitted time</b></td><td>{7}</td></tr>"""
 1754             """<tr><td><b>Last author</b></td><td>{8}</td></tr>"""
 1755             """</table>"""
 1756         ).format(
 1757             ".".join([str(v) for v in pysvn.version]),
 1758             ".".join([str(v) for v in pysvn.svn_version[:3]]),
 1759             apiVersion,
 1760             entry.url,
 1761             entry.revision.number,
 1762             entry.commit_revision.number,
 1763             time.strftime(
 1764                 "%Y-%m-%d", time.localtime(entry.commit_time)),
 1765             hmsz,
 1766             entry.commit_author
 1767         )
 1768     
 1769     ###########################################################################
 1770     ## Public Subversion specific methods are below.
 1771     ###########################################################################
 1772     
 1773     def svnGetReposName(self, path):
 1774         """
 1775         Public method used to retrieve the URL of the subversion repository
 1776         path.
 1777         
 1778         @param path local path to get the svn repository path for (string)
 1779         @return string with the repository path URL
 1780         """
 1781         client = pysvn.Client()
 1782         locker = QMutexLocker(self.vcsExecutionMutex)
 1783         try:
 1784             entry = client.info(path)
 1785             url = entry.url
 1786         except pysvn.ClientError:
 1787             url = ""
 1788         locker.unlock()
 1789         return url
 1790 
 1791     def svnResolve(self, name):
 1792         """
 1793         Public method used to resolve conflicts of a file/directory.
 1794         
 1795         @param name file/directory name to be resolved (string)
 1796         """
 1797         if isinstance(name, list):
 1798             dname, fnames = self.splitPathList(name)
 1799         else:
 1800             dname, fname = self.splitPath(name)
 1801             fnames = [fname]
 1802         
 1803         locker = QMutexLocker(self.vcsExecutionMutex)
 1804         cwd = os.getcwd()
 1805         os.chdir(dname)
 1806         opts = self.options['global']
 1807         recurse = "--non-recursive" not in opts
 1808         client = self.getClient()
 1809         dlg = SvnDialog(self.tr('Resolving conficts'),
 1810                         "resolved{0} {1}".format(
 1811                             (not recurse) and " --non-recursive" or "",
 1812                             " ".join(fnames)),
 1813                         client)
 1814         QApplication.processEvents()
 1815         try:
 1816             for name in fnames:
 1817                 client.resolved(name, recurse=recurse)
 1818         except pysvn.ClientError as e:
 1819             dlg.showError(e.args[0])
 1820         locker.unlock()
 1821         dlg.finish()
 1822         dlg.exec_()
 1823         os.chdir(cwd)
 1824         self.checkVCSStatus()
 1825     
 1826     def svnCopy(self, name, project):
 1827         """
 1828         Public method used to copy a file/directory.
 1829         
 1830         @param name file/directory name to be copied (string)
 1831         @param project reference to the project object
 1832         @return flag indicating successfull operation (boolean)
 1833         """
 1834         from .SvnCopyDialog import SvnCopyDialog
 1835         rx_prot = QRegExp('(file:|svn:|svn+ssh:|http:|https:).+')
 1836         dlg = SvnCopyDialog(name)
 1837         res = False
 1838         if dlg.exec_() == QDialog.Accepted:
 1839             target, force = dlg.getData()
 1840             
 1841             client = self.getClient()
 1842             if rx_prot.exactMatch(target):
 1843                 target = self.__svnURL(target)
 1844                 log = "Copying {0} to {1}".format(name, target)
 1845             else:
 1846                 log = ""
 1847                 target = target
 1848             dlg = SvnDialog(
 1849                 self.tr('Copying {0}').format(name),
 1850                 "copy{0} {1} {2}".format(
 1851                     log and (" --message {0}".format(log)) or "",
 1852                     name, target),
 1853                 client, log=log)
 1854             QApplication.processEvents()
 1855             locker = QMutexLocker(self.vcsExecutionMutex)
 1856             try:
 1857                 client.copy(name, target)
 1858                 res = True
 1859             except pysvn.ClientError as e:
 1860                 res = False
 1861                 dlg.showError(e.args[0])
 1862             locker.unlock()
 1863             dlg.finish()
 1864             dlg.exec_()
 1865             if (
 1866                 res and
 1867                 not rx_prot.exactMatch(target) and
 1868                 target.startswith(project.getProjectPath())
 1869             ):
 1870                 if os.path.isdir(name):
 1871                     project.copyDirectory(name, target)
 1872                 else:
 1873                     project.appendFile(target)
 1874         return res
 1875     
 1876     def svnListProps(self, name, recursive=False):
 1877         """
 1878         Public method used to list the properties of a file/directory.
 1879         
 1880         @param name file/directory name (string or list of strings)
 1881         @param recursive flag indicating a recursive list is requested
 1882         """
 1883         if self.propList is None:
 1884             from .SvnPropListDialog import SvnPropListDialog
 1885             self.propList = SvnPropListDialog(self)
 1886         self.propList.show()
 1887         self.propList.raise_()
 1888         QApplication.processEvents()
 1889         self.propList.start(name, recursive)
 1890         
 1891     def svnSetProp(self, name, recursive=False):
 1892         """
 1893         Public method used to add a property to a file/directory.
 1894         
 1895         @param name file/directory name (string or list of strings)
 1896         @param recursive flag indicating a recursive set is requested
 1897         """
 1898         from .SvnPropSetDialog import SvnPropSetDialog
 1899         dlg = SvnPropSetDialog(recursive)
 1900         if dlg.exec_() == QDialog.Accepted:
 1901             propName, propValue, recurse = dlg.getData()
 1902             if not propName:
 1903                 E5MessageBox.critical(
 1904                     self.__ui,
 1905                     self.tr("Subversion Set Property"),
 1906                     self.tr(
 1907                         """You have to supply a property name. Aborting."""))
 1908                 return
 1909             
 1910             if isinstance(name, list):
 1911                 dname, fnames = self.splitPathList(name)
 1912             else:
 1913                 dname, fname = self.splitPath(name)
 1914                 fnames = [fname]
 1915             
 1916             locker = QMutexLocker(self.vcsExecutionMutex)
 1917             cwd = os.getcwd()
 1918             os.chdir(dname)
 1919             opts = self.options['global']
 1920             skipchecks = "--skip-checks" in opts
 1921             client = self.getClient()
 1922             dlg = SvnDialog(
 1923                 self.tr('Subversion Set Property'),
 1924                 "propset{0}{1} {2} {3} {4}".format(
 1925                     recurse and " --recurse" or "",
 1926                     skipchecks and " --skip-checks" or "",
 1927                     propName, propValue,
 1928                     " ".join(fnames)),
 1929                 client)
 1930             QApplication.processEvents()
 1931             try:
 1932                 for name in fnames:
 1933                     client.propset(propName, propValue, name,
 1934                                    recurse=recurse, skip_checks=skipchecks)
 1935             except pysvn.ClientError as e:
 1936                 dlg.showError(e.args[0])
 1937             locker.unlock()
 1938             dlg.showMessage(self.tr("Property set."))
 1939             dlg.finish()
 1940             dlg.exec_()
 1941             os.chdir(cwd)
 1942         
 1943     def svnDelProp(self, name, recursive=False):
 1944         """
 1945         Public method used to delete a property of a file/directory.
 1946         
 1947         @param name file/directory name (string or list of strings)
 1948         @param recursive flag indicating a recursive list is requested
 1949         """
 1950         from .SvnPropDelDialog import SvnPropDelDialog
 1951         dlg = SvnPropDelDialog(recursive)
 1952         if dlg.exec_() == QDialog.Accepted:
 1953             propName, recurse = dlg.getData()
 1954             
 1955             if not propName:
 1956                 E5MessageBox.critical(
 1957                     self.__ui,
 1958                     self.tr("Subversion Delete Property"),
 1959                     self.tr(
 1960                         """You have to supply a property name. Aborting."""))
 1961                 return
 1962             
 1963             if isinstance(name, list):
 1964                 dname, fnames = self.splitPathList(name)
 1965             else:
 1966                 dname, fname = self.splitPath(name)
 1967                 fnames = [fname]
 1968             
 1969             locker = QMutexLocker(self.vcsExecutionMutex)
 1970             cwd = os.getcwd()
 1971             os.chdir(dname)
 1972             opts = self.options['global']
 1973             skipchecks = "--skip-checks" in opts
 1974             client = self.getClient()
 1975             dlg = SvnDialog(
 1976                 self.tr('Subversion Delete Property'),
 1977                 "propdel{0}{1} {2} {3}".format(
 1978                     recurse and " --recurse" or "",
 1979                     skipchecks and " --skip-checks" or "",
 1980                     propName, " ".join(fnames)),
 1981                 client)
 1982             QApplication.processEvents()
 1983             try:
 1984                 for name in fnames:
 1985                     client.propdel(propName, name,
 1986                                    recurse=recurse, skip_checks=skipchecks)
 1987             except pysvn.ClientError as e:
 1988                 dlg.showError(e.args[0])
 1989             locker.unlock()
 1990             dlg.showMessage(self.tr("Property deleted."))
 1991             dlg.finish()
 1992             dlg.exec_()
 1993             os.chdir(cwd)
 1994         
 1995     def svnListTagBranch(self, path, tags=True):
 1996         """
 1997         Public method used to list the available tags or branches.
 1998         
 1999         @param path directory name of the project (string)
 2000         @param tags flag indicating listing of branches or tags
 2001                 (False = branches, True = tags)
 2002         """
 2003         if self.tagbranchList is None:
 2004             from .SvnTagBranchListDialog import SvnTagBranchListDialog
 2005             self.tagbranchList = SvnTagBranchListDialog(self)
 2006         self.tagbranchList.show()
 2007         self.tagbranchList.raise_()
 2008         QApplication.processEvents()
 2009         res = self.tagbranchList.start(path, tags)
 2010         if res:
 2011             if tags:
 2012                 self.tagsList = self.tagbranchList.getTagList()
 2013                 if not self.showedTags:
 2014                     self.allTagsBranchesList = (
 2015                         self.allTagsBranchesList +
 2016                         self.tagsList
 2017                     )
 2018                     self.showedTags = True
 2019             elif not tags:
 2020                 self.branchesList = self.tagbranchList.getTagList()
 2021                 if not self.showedBranches:
 2022                     self.allTagsBranchesList = (
 2023                         self.allTagsBranchesList +
 2024                         self.branchesList
 2025                     )
 2026                     self.showedBranches = True
 2027         
 2028     def svnBlame(self, name):
 2029         """
 2030         Public method to show the output of the svn blame command.
 2031         
 2032         @param name file name to show the blame for (string)
 2033         """
 2034         if self.blame is None:
 2035             from .SvnBlameDialog import SvnBlameDialog
 2036             self.blame = SvnBlameDialog(self)
 2037         self.blame.show()
 2038         self.blame.raise_()
 2039         QApplication.processEvents()
 2040         self.blame.start(name)
 2041         
 2042     def svnExtendedDiff(self, name):
 2043         """
 2044         Public method used to view the difference of a file/directory to the
 2045         Subversion repository.
 2046         
 2047         If name is a directory and is the project directory, all project files
 2048         are saved first. If name is a file (or list of files), which is/are
 2049         being edited and has unsaved modification, they can be saved or the
 2050         operation may be aborted.
 2051         
 2052         This method gives the chance to enter the revisions to be compared.
 2053         
 2054         @param name file/directory name to be diffed (string)
 2055         """
 2056         if isinstance(name, list):
 2057             names = name[:]
 2058         else:
 2059             names = [name]
 2060         for nam in names:
 2061             if os.path.isfile(nam):
 2062                 editor = e5App().getObject("ViewManager").getOpenEditor(nam)
 2063                 if editor and not editor.checkDirty():
 2064                     return
 2065             else:
 2066                 project = e5App().getObject("Project")
 2067                 if nam == project.ppath and not project.saveAllScripts():
 2068                     return
 2069         from .SvnRevisionSelectionDialog import SvnRevisionSelectionDialog
 2070         dlg = SvnRevisionSelectionDialog()
 2071         if dlg.exec_() == QDialog.Accepted:
 2072             revisions = dlg.getRevisions()
 2073             if self.diff is None:
 2074                 from .SvnDiffDialog import SvnDiffDialog
 2075                 self.diff = SvnDiffDialog(self)
 2076             self.diff.show()
 2077             self.diff.raise_()
 2078             QApplication.processEvents()
 2079             self.diff.start(name, revisions)
 2080         
 2081     def svnUrlDiff(self, name):
 2082         """
 2083         Public method used to view the difference of a file/directory of two
 2084         repository URLs.
 2085         
 2086         If name is a directory and is the project directory, all project files
 2087         are saved first. If name is a file (or list of files), which is/are
 2088         being edited and has unsaved modification, they can be saved or the
 2089         operation may be aborted.
 2090         
 2091         This method gives the chance to enter the revisions to be compared.
 2092         
 2093         @param name file/directory name to be diffed (string)
 2094         """
 2095         if isinstance(name, list):
 2096             names = name[:]
 2097         else:
 2098             names = [name]
 2099         for nam in names:
 2100             if os.path.isfile(nam):
 2101                 editor = e5App().getObject("ViewManager").getOpenEditor(nam)
 2102                 if editor and not editor.checkDirty():
 2103                     return
 2104             else:
 2105                 project = e5App().getObject("Project")
 2106                 if nam == project.ppath and not project.saveAllScripts():
 2107                     return
 2108         
 2109         dname = self.splitPath(names[0])[0]
 2110         
 2111         from .SvnUrlSelectionDialog import SvnUrlSelectionDialog
 2112         dlg = SvnUrlSelectionDialog(self, self.tagsList, self.branchesList,
 2113                                     dname)
 2114         if dlg.exec_() == QDialog.Accepted:
 2115             urls, summary = dlg.getURLs()
 2116             if self.diff is None:
 2117                 from .SvnDiffDialog import SvnDiffDialog
 2118                 self.diff = SvnDiffDialog(self)
 2119             self.diff.show()
 2120             self.diff.raise_()
 2121             QApplication.processEvents()
 2122             self.diff.start(name, urls=urls, summary=summary)
 2123         
 2124     def __svnGetFileForRevision(self, name, rev=""):
 2125         """
 2126         Private method to get a file for a specific revision from the
 2127         repository.
 2128         
 2129         @param name file name to get from the repository (string)
 2130         @keyparam rev revision to retrieve (integer or string)
 2131         @return contents of the file (string) and an error message (string)
 2132         """
 2133         output = ""
 2134         error = ""
 2135         
 2136         client = self.getClient()
 2137         try:
 2138             if rev:
 2139                 if isinstance(rev, int) or rev.isdecimal():
 2140                     rev = pysvn.Revision(
 2141                         pysvn.opt_revision_kind.number, int(rev))
 2142                 elif rev.startswith("{"):
 2143                     dateStr = rev[1:-1]
 2144                     secs = QDateTime.fromString(dateStr, Qt.ISODate).toTime_t()
 2145                     rev = pysvn.Revision(pysvn.opt_revision_kind.date, secs)
 2146                 elif rev == "HEAD":
 2147                     rev = pysvn.Revision(pysvn.opt_revision_kind.head)
 2148                 elif rev == "COMMITTED":
 2149                     rev = pysvn.Revision(pysvn.opt_revision_kind.committed)
 2150                 elif rev == "BASE":
 2151                     rev = pysvn.Revision(pysvn.opt_revision_kind.base)
 2152                 elif rev == "WORKING":
 2153                     rev = pysvn.Revision(pysvn.opt_revision_kind.working)
 2154                 elif rev == "PREV":
 2155                     rev = pysvn.Revision(pysvn.opt_revision_kind.previous)
 2156                 else:
 2157                     rev = pysvn.Revision(pysvn.opt_revision_kind.unspecified)
 2158                 output = client.cat(name, revision=rev)
 2159             else:
 2160                 output = client.cat(name)
 2161             output = output.decode('utf-8')
 2162         except pysvn.ClientError as e:
 2163             error = str(e)
 2164         
 2165         return output, error
 2166     
 2167     def svnSbsDiff(self, name, extended=False, revisions=None):
 2168         """
 2169         Public method used to view the difference of a file to the Mercurial
 2170         repository side-by-side.
 2171         
 2172         @param name file name to be diffed (string)
 2173         @keyparam extended flag indicating the extended variant (boolean)
 2174         @keyparam revisions tuple of two revisions (tuple of strings)
 2175         @exception ValueError raised to indicate an invalid name parameter type
 2176         """
 2177         if isinstance(name, list):
 2178             raise ValueError("Wrong parameter type")
 2179         
 2180         if extended:
 2181             from .SvnRevisionSelectionDialog import SvnRevisionSelectionDialog
 2182             dlg = SvnRevisionSelectionDialog()
 2183             if dlg.exec_() == QDialog.Accepted:
 2184                 rev1, rev2 = dlg.getRevisions()
 2185                 if rev1 == "WORKING":
 2186                     rev1 = ""
 2187                 if rev2 == "WORKING":
 2188                     rev2 = ""
 2189             else:
 2190                 return
 2191         elif revisions:
 2192             rev1, rev2 = revisions[0], revisions[1]
 2193         else:
 2194             rev1, rev2 = "", ""
 2195         
 2196         output1, error = self.__svnGetFileForRevision(name, rev=rev1)
 2197         if error:
 2198             E5MessageBox.critical(
 2199                 self.__ui,
 2200                 self.tr("Subversion Side-by-Side Difference"),
 2201                 error)
 2202             return
 2203         name1 = "{0} (rev. {1})".format(name, rev1 and rev1 or ".")
 2204         
 2205         if rev2:
 2206             output2, error = self.__svnGetFileForRevision(name, rev=rev2)
 2207             if error:
 2208                 E5MessageBox.critical(
 2209                     self.__ui,
 2210                     self.tr("Subversion Side-by-Side Difference"),
 2211                     error)
 2212                 return
 2213             name2 = "{0} (rev. {1})".format(name, rev2)
 2214         else:
 2215             try:
 2216                 f1 = open(name, "r", encoding="utf-8")
 2217                 output2 = f1.read()
 2218                 f1.close()
 2219                 name2 = name
 2220             except IOError:
 2221                 E5MessageBox.critical(
 2222                     self.__ui,
 2223                     self.tr("Subversion Side-by-Side Difference"),
 2224                     self.tr(
 2225                         """<p>The file <b>{0}</b> could not be read.</p>""")
 2226                     .format(name))
 2227                 return
 2228         
 2229         if self.sbsDiff is None:
 2230             from UI.CompareDialog import CompareDialog
 2231             self.sbsDiff = CompareDialog()
 2232         self.sbsDiff.show()
 2233         self.sbsDiff.raise_()
 2234         self.sbsDiff.compare(output1, output2, name1, name2)
 2235     
 2236     def vcsLogBrowser(self, name, isFile=False):
 2237         """
 2238         Public method used to browse the log of a file/directory from the
 2239         Subversion repository.
 2240         
 2241         @param name file/directory name to show the log of (string)
 2242         @param isFile flag indicating log for a file is to be shown (boolean)
 2243         """
 2244         if self.logBrowser is None:
 2245             from .SvnLogBrowserDialog import SvnLogBrowserDialog
 2246             self.logBrowser = SvnLogBrowserDialog(self)
 2247         self.logBrowser.show()
 2248         self.logBrowser.raise_()
 2249         QApplication.processEvents()
 2250         self.logBrowser.start(name, isFile=isFile)
 2251         
 2252     def svnLock(self, name, stealIt=False, parent=None):
 2253         """
 2254         Public method used to lock a file in the Subversion repository.
 2255         
 2256         @param name file/directory name to be locked (string or list of
 2257             strings)
 2258         @param stealIt flag indicating a forced operation (boolean)
 2259         @param parent reference to the parent object of the subversion dialog
 2260             (QWidget)
 2261         """
 2262         comment, ok = QInputDialog.getText(
 2263             None,
 2264             self.tr("Subversion Lock"),
 2265             self.tr("Enter lock comment"),
 2266             QLineEdit.Normal)
 2267         
 2268         if not ok:
 2269             return
 2270         
 2271         if isinstance(name, list):
 2272             dname, fnames = self.splitPathList(name)
 2273         else:
 2274             dname, fname = self.splitPath(name)
 2275             fnames = [fname]
 2276         
 2277         locker = QMutexLocker(self.vcsExecutionMutex)
 2278         cwd = os.getcwd()
 2279         os.chdir(dname)
 2280         client = self.getClient()
 2281         dlg = SvnDialog(
 2282             self.tr('Locking in the Subversion repository'),
 2283             "lock{0}{1} {2}".format(
 2284                 stealIt and " --force" or "",
 2285                 comment and (" --message {0}".format(comment)) or "",
 2286                 " ".join(fnames)),
 2287             client, parent=parent)
 2288         QApplication.processEvents()
 2289         try:
 2290             client.lock(fnames, comment, force=stealIt)
 2291         except pysvn.ClientError as e:
 2292             dlg.showError(e.args[0])
 2293         except AttributeError as e:
 2294             dlg.showError(str(e))
 2295         locker.unlock()
 2296         dlg.finish()
 2297         dlg.exec_()
 2298         os.chdir(cwd)
 2299         
 2300     def svnUnlock(self, name, breakIt=False, parent=None):
 2301         """
 2302         Public method used to unlock a file in the Subversion repository.
 2303         
 2304         @param name file/directory name to be unlocked (string or list of
 2305             strings)
 2306         @param breakIt flag indicating a forced operation (boolean)
 2307         @param parent reference to the parent object of the subversion dialog
 2308             (QWidget)
 2309         """
 2310         if isinstance(name, list):
 2311             dname, fnames = self.splitPathList(name)
 2312         else:
 2313             dname, fname = self.splitPath(name)
 2314             fnames = [fname]
 2315         
 2316         locker = QMutexLocker(self.vcsExecutionMutex)
 2317         cwd = os.getcwd()
 2318         os.chdir(dname)
 2319         client = self.getClient()
 2320         dlg = SvnDialog(
 2321             self.tr('Unlocking in the Subversion repository'),
 2322             "unlock{0} {1}".format(breakIt and " --force" or "",
 2323                                    " ".join(fnames)),
 2324             client, parent=parent)
 2325         QApplication.processEvents()
 2326         try:
 2327             client.unlock(fnames, force=breakIt)
 2328         except pysvn.ClientError as e:
 2329             dlg.showError(e.args[0])
 2330         except AttributeError as e:
 2331             dlg.showError(str(e))
 2332         locker.unlock()
 2333         dlg.finish()
 2334         dlg.exec_()
 2335         os.chdir(cwd)
 2336         
 2337     def svnInfo(self, projectPath, name):
 2338         """
 2339         Public method to show repository information about a file or directory.
 2340         
 2341         @param projectPath path name of the project (string)
 2342         @param name file/directory name relative to the project (string)
 2343         """
 2344         from .SvnInfoDialog import SvnInfoDialog
 2345         dlg = SvnInfoDialog(self)
 2346         dlg.start(projectPath, name)
 2347         dlg.exec_()
 2348         
 2349     def svnRelocate(self, projectPath):
 2350         """
 2351         Public method to relocate the working copy to a new repository URL.
 2352         
 2353         @param projectPath path name of the project (string)
 2354         """
 2355         from .SvnRelocateDialog import SvnRelocateDialog
 2356         currUrl = self.svnGetReposName(projectPath)
 2357         dlg = SvnRelocateDialog(currUrl)
 2358         if dlg.exec_() == QDialog.Accepted:
 2359             newUrl, inside = dlg.getData()
 2360             if inside:
 2361                 msg = "switch {0} {1}".format(newUrl, projectPath)
 2362             else:
 2363                 msg = "relocate {0} {1} {2}".format(currUrl, newUrl,
 2364                                                     projectPath)
 2365             client = self.getClient()
 2366             dlg = SvnDialog(self.tr('Relocating'), msg, client)
 2367             QApplication.processEvents()
 2368             locker = QMutexLocker(self.vcsExecutionMutex)
 2369             try:
 2370                 if inside:
 2371                     client.switch(projectPath, newUrl)
 2372                 else:
 2373                     client.relocate(currUrl, newUrl, projectPath, recurse=True)
 2374             except pysvn.ClientError as e:
 2375                 dlg.showError(e.args[0])
 2376             locker.unlock()
 2377             dlg.finish()
 2378             dlg.exec_()
 2379         
 2380     def svnRepoBrowser(self, projectPath=None):
 2381         """
 2382         Public method to open the repository browser.
 2383         
 2384         @param projectPath path name of the project (string)
 2385         """
 2386         if projectPath:
 2387             url = self.svnGetReposName(projectPath)
 2388         else:
 2389             url = None
 2390         
 2391         if url is None:
 2392             url, ok = QInputDialog.getText(
 2393                 None,
 2394                 self.tr("Repository Browser"),
 2395                 self.tr("Enter the repository URL."),
 2396                 QLineEdit.Normal)
 2397             if not ok or not url:
 2398                 return
 2399         
 2400         if self.repoBrowser is None:
 2401             from .SvnRepoBrowserDialog import SvnRepoBrowserDialog
 2402             self.repoBrowser = SvnRepoBrowserDialog(self)
 2403         self.repoBrowser.start(url)
 2404         self.repoBrowser.show()
 2405         self.repoBrowser.raise_()
 2406         
 2407     def svnRemoveFromChangelist(self, names):
 2408         """
 2409         Public method to remove a file or directory from its changelist.
 2410         
 2411         Note: Directories will be removed recursively.
 2412         
 2413         @param names name or list of names of file or directory to remove
 2414             (string)
 2415         """
 2416         if not isinstance(names, list):
 2417             names = [names]
 2418         client = self.getClient()
 2419         dlg = SvnDialog(
 2420             self.tr('Remove from changelist'),
 2421             "changelist --remove {0}".format(" ".join(names)),
 2422             client)
 2423         QApplication.processEvents()
 2424         locker = QMutexLocker(self.vcsExecutionMutex)
 2425         try:
 2426             for name in names:
 2427                 client.remove_from_changelists(name)
 2428         except pysvn.ClientError as e:
 2429             dlg.showError(e.args[0])
 2430         locker.unlock()
 2431         dlg.finish()
 2432         dlg.exec_()
 2433         
 2434     def svnAddToChangelist(self, names):
 2435         """
 2436         Public method to add a file or directory to a changelist.
 2437         
 2438         Note: Directories will be added recursively.
 2439         
 2440         @param names name or list of names of file or directory to add
 2441             (string)
 2442         """
 2443         if not isinstance(names, list):
 2444             names = [names]
 2445         
 2446         clname, ok = QInputDialog.getItem(
 2447             None,
 2448             self.tr("Add to changelist"),
 2449             self.tr("Enter name of the changelist:"),
 2450             sorted(self.svnGetChangelists()),
 2451             0, True)
 2452         if not ok or not clname:
 2453             return
 2454 
 2455         client = self.getClient()
 2456         dlg = SvnDialog(
 2457             self.tr('Add to changelist'),
 2458             "changelist {0}".format(" ".join(names)),
 2459             client)
 2460         QApplication.processEvents()
 2461         locker = QMutexLocker(self.vcsExecutionMutex)
 2462         try:
 2463             for name in names:
 2464                 client.add_to_changelist(name, clname,
 2465                                          depth=pysvn.depth.infinity)
 2466         except pysvn.ClientError as e:
 2467             dlg.showError(e.args[0])
 2468         locker.unlock()
 2469         dlg.finish()
 2470         dlg.exec_()
 2471     
 2472     def svnShowChangelists(self, path):
 2473         """
 2474         Public method used to inspect the change lists defined for the project.
 2475         
 2476         @param path directory name to show change lists for (string)
 2477         """
 2478         from .SvnChangeListsDialog import SvnChangeListsDialog
 2479         self.changeLists = SvnChangeListsDialog(self)
 2480         self.changeLists.show()
 2481         QApplication.processEvents()
 2482         self.changeLists.start(path)
 2483         
 2484     def svnGetChangelists(self):
 2485         """
 2486         Public method to get a list of all defined change lists.
 2487         
 2488         @return list of defined change list names (list of strings)
 2489         """
 2490         changelists = []
 2491         client = self.getClient()
 2492         if hasattr(client, 'get_changelist'):
 2493             ppath = e5App().getObject("Project").getProjectPath()
 2494             locker = QMutexLocker(self.vcsExecutionMutex)
 2495             try:
 2496                 entries = client.get_changelist(ppath,
 2497                                                 depth=pysvn.depth.infinity)
 2498                 for entry in entries:
 2499                     changelist = entry[1]
 2500                     if changelist not in changelists:
 2501                         changelists.append(changelist)
 2502             except pysvn.ClientError:
 2503                 pass
 2504             locker.unlock()
 2505         
 2506         return changelists
 2507         
 2508     def svnUpgrade(self, path):
 2509         """
 2510         Public method to upgrade the working copy format.
 2511         
 2512         @param path directory name to show change lists for (string)
 2513         """
 2514         client = self.getClient()
 2515         dlg = SvnDialog(
 2516             self.tr('Upgrade'),
 2517             "upgrade {0}".format(path),
 2518             client)
 2519         QApplication.processEvents()
 2520         locker = QMutexLocker(self.vcsExecutionMutex)
 2521         try:
 2522             client.upgrade(path)
 2523         except pysvn.ClientError as e:
 2524             dlg.showError(e.args[0])
 2525         locker.unlock()
 2526         dlg.finish()
 2527         dlg.exec_()
 2528 
 2529     ###########################################################################
 2530     ## Private Subversion specific methods are below.
 2531     ###########################################################################
 2532     
 2533     def __svnURL(self, url):
 2534         """
 2535         Private method to format a url for subversion.
 2536         
 2537         @param url unformatted url string (string)
 2538         @return properly formated url for subversion (string)
 2539         """
 2540         url = self.svnNormalizeURL(url)
 2541         url = url.split(':', 2)
 2542         if len(url) == 3:
 2543             scheme = url[0]
 2544             host = url[1]
 2545             port, path = url[2].split("/", 1)
 2546             return "{0}:{1}:{2}/{3}".format(scheme, host, port, quote(path))
 2547         else:
 2548             scheme = url[0]
 2549             if scheme == "file":
 2550                 return "{0}:{1}".format(scheme, quote(url[1]))
 2551             else:
 2552                 try:
 2553                     host, path = url[1][2:].split("/", 1)
 2554                 except ValueError:
 2555                     host = url[1][2:]
 2556                     path = ""
 2557                 return "{0}://{1}/{2}".format(scheme, host, quote(path))
 2558 
 2559     def svnNormalizeURL(self, url):
 2560         """
 2561         Public method to normalize a url for subversion.
 2562         
 2563         @param url url string (string)
 2564         @return properly normalized url for subversion (string)
 2565         """
 2566         protocol, url = url.split("://", 1)
 2567         if url.startswith("\\\\"):
 2568             url = url[2:]
 2569         if protocol == "file":
 2570             url = os.path.normcase(url)
 2571             if url[1] == ":":
 2572                 url = url.replace(":", "|", 1)
 2573         url = url.replace('\\', '/')
 2574         if url.endswith('/'):
 2575             url = url[:-1]
 2576         if not url.startswith("/") and url[1] in [":", "|"]:
 2577             url = "/{0}".format(url)
 2578         return "{0}://{1}".format(protocol, url)
 2579 
 2580     ###########################################################################
 2581     ## Methods to get the helper objects are below.
 2582     ###########################################################################
 2583     
 2584     def vcsGetProjectBrowserHelper(self, browser, project,
 2585                                    isTranslationsBrowser=False):
 2586         """
 2587         Public method to instanciate a helper object for the different
 2588         project browsers.
 2589         
 2590         @param browser reference to the project browser object
 2591         @param project reference to the project object
 2592         @param isTranslationsBrowser flag indicating, the helper is requested
 2593             for the translations browser (this needs some special treatment)
 2594         @return the project browser helper object
 2595         """
 2596         from .ProjectBrowserHelper import SvnProjectBrowserHelper
 2597         return SvnProjectBrowserHelper(self, browser, project,
 2598                                        isTranslationsBrowser)
 2599         
 2600     def vcsGetProjectHelper(self, project):
 2601         """
 2602         Public method to instanciate a helper object for the project.
 2603         
 2604         @param project reference to the project object
 2605         @return the project helper object
 2606         """
 2607         helper = self.__plugin.getProjectHelper()
 2608         helper.setObjects(self, project)
 2609         self.__wcng = (
 2610             os.path.exists(
 2611                 os.path.join(project.getProjectPath(), ".svn", "format")) or
 2612             os.path.exists(
 2613                 os.path.join(project.getProjectPath(), "_svn", "format")) or
 2614             os.path.exists(
 2615                 os.path.join(project.getProjectPath(), ".svn", "wc.db")) or
 2616             os.path.exists(
 2617                 os.path.join(project.getProjectPath(), "_svn", "wc.db"))
 2618         )
 2619         return helper
 2620 
 2621     ###########################################################################
 2622     ##  Status Monitor Thread methods
 2623     ###########################################################################
 2624 
 2625     def _createStatusMonitorThread(self, interval, project):
 2626         """
 2627         Protected method to create an instance of the VCS status monitor
 2628         thread.
 2629         
 2630         @param interval check interval for the monitor thread in seconds
 2631             (integer)
 2632         @param project reference to the project object
 2633         @return reference to the monitor thread (QThread)
 2634         """
 2635         from .SvnStatusMonitorThread import SvnStatusMonitorThread
 2636         return SvnStatusMonitorThread(interval, project, self)