"Fossies" - the Fresh Open Source Software Archive

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