"Fossies" - the Fresh Open Source Software Archive

Member "eric6-20.9/eric/eric6/Plugins/VcsPlugins/vcsMercurial/hg.py" (2 Sep 2020, 138131 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 "hg.py" see the Fossies "Dox" file reference documentation.

    1 # -*- coding: utf-8 -*-
    2 
    3 # Copyright (c) 2010 - 2020 Detlev Offenbach <detlev@die-offenbachs.de>
    4 #
    5 
    6 """
    7 Module implementing the version control systems interface to Mercurial.
    8 """
    9 
   10 import os
   11 import shutil
   12 
   13 from PyQt5.QtCore import (
   14     QProcess, pyqtSignal, QFileInfo, QFileSystemWatcher, QCoreApplication
   15 )
   16 from PyQt5.QtWidgets import QApplication, QDialog, QInputDialog
   17 
   18 from E5Gui.E5Application import e5App
   19 from E5Gui import E5MessageBox, E5FileDialog
   20 
   21 from QScintilla.MiniEditor import MiniEditor
   22 
   23 from VCS.VersionControl import VersionControl
   24 from VCS.RepositoryInfoDialog import VcsRepositoryInfoDialog
   25 
   26 from .HgDialog import HgDialog
   27 from .HgClient import HgClient
   28 
   29 import Utilities
   30 
   31 
   32 class Hg(VersionControl):
   33     """
   34     Class implementing the version control systems interface to Mercurial.
   35     
   36     @signal committed() emitted after the commit action has completed
   37     @signal activeExtensionsChanged() emitted when the list of active
   38         extensions has changed
   39     @signal iniFileChanged() emitted when a Mercurial/repo configuration file
   40         has changed
   41     """
   42     committed = pyqtSignal()
   43     activeExtensionsChanged = pyqtSignal()
   44     iniFileChanged = pyqtSignal()
   45     
   46     IgnoreFileName = ".hgignore"
   47     
   48     def __init__(self, plugin, parent=None, name=None):
   49         """
   50         Constructor
   51         
   52         @param plugin reference to the plugin object
   53         @param parent parent widget (QWidget)
   54         @param name name of this object (string)
   55         """
   56         VersionControl.__init__(self, parent, name)
   57         self.defaultOptions = {
   58             'global': [''],
   59             'commit': [''],
   60             'checkout': [''],
   61             'update': [''],
   62             'add': [''],
   63             'remove': [''],
   64             'diff': [''],
   65             'log': [''],
   66             'history': [''],
   67             'status': [''],
   68             'tag': [''],
   69             'export': ['']
   70         }
   71         
   72         self.__plugin = plugin
   73         self.__ui = parent
   74         
   75         self.options = self.defaultOptions
   76         self.tagsList = []
   77         self.branchesList = []
   78         self.allTagsBranchesList = []
   79         self.bookmarksList = []
   80         self.showedTags = False
   81         self.showedBranches = False
   82         
   83         self.tagTypeList = [
   84             'tags',
   85             'branches',
   86         ]
   87         
   88         self.commandHistory = []
   89         
   90         if "HG_ASP_DOT_NET_HACK" in os.environ:
   91             self.adminDir = '_hg'
   92         else:
   93             self.adminDir = '.hg'
   94         
   95         self.logBrowser = None
   96         self.logBrowserIncoming = None
   97         self.logBrowserOutgoing = None
   98         self.diff = None
   99         self.sbsDiff = None
  100         self.status = None
  101         self.summary = None
  102         self.tagbranchList = None
  103         self.annotate = None
  104         self.repoEditor = None
  105         self.serveDlg = None
  106         self.bookmarksListDlg = None
  107         self.bookmarksInOutDlg = None
  108         self.conflictsDlg = None
  109         
  110         self.bundleFile = None
  111         self.__lastChangeGroupPath = None
  112         
  113         self.statusCache = {}
  114         
  115         self.__commitData = {}
  116         self.__commitDialog = None
  117         
  118         self.__forgotNames = []
  119         
  120         self.__activeExtensions = []
  121         
  122         from .HgUtilities import getConfigPath
  123         self.__iniWatcher = QFileSystemWatcher(self)
  124         self.__iniWatcher.fileChanged.connect(self.__iniFileChanged)
  125         cfgFile = getConfigPath()
  126         if os.path.exists(cfgFile):
  127             self.__iniWatcher.addPath(cfgFile)
  128         
  129         self.__client = None
  130         self.__createClient()
  131         self.__projectHelper = None
  132         
  133         self.__repoDir = ""
  134         self.__repoIniFile = ""
  135         self.__defaultConfigured = False
  136         self.__defaultPushConfigured = False
  137         
  138         # instantiate the extensions
  139         from .QueuesExtension.queues import Queues
  140         from .PurgeExtension.purge import Purge
  141         from .GpgExtension.gpg import Gpg
  142         from .RebaseExtension.rebase import Rebase
  143         from .ShelveExtension.shelve import Shelve
  144         from .LargefilesExtension.largefiles import Largefiles
  145         from .StripExtension.strip import Strip
  146         from .HisteditExtension.histedit import Histedit
  147         from .CloseheadExtension.closehead import Closehead
  148         self.__extensions = {
  149             "mq": Queues(self),
  150             "purge": Purge(self),
  151             "gpg": Gpg(self),
  152             "rebase": Rebase(self),
  153             "shelve": Shelve(self),
  154             "largefiles": Largefiles(self),
  155             "strip": Strip(self),
  156             "histedit": Histedit(self),
  157             "closehead": Closehead(self),
  158         }
  159     
  160     def getPlugin(self):
  161         """
  162         Public method to get a reference to the plugin object.
  163         
  164         @return reference to the plugin object (VcsMercurialPlugin)
  165         """
  166         return self.__plugin
  167     
  168     def getEncoding(self):
  169         """
  170         Public method to get the encoding to be used by Mercurial.
  171         
  172         @return encoding (string)
  173         """
  174         return self.__plugin.getPreferences("Encoding")
  175     
  176     def vcsShutdown(self):
  177         """
  178         Public method used to shutdown the Mercurial interface.
  179         """
  180         if self.logBrowser is not None:
  181             self.logBrowser.close()
  182         if self.logBrowserIncoming is not None:
  183             self.logBrowserIncoming.close()
  184         if self.logBrowserOutgoing is not None:
  185             self.logBrowserOutgoing.close()
  186         if self.diff is not None:
  187             self.diff.close()
  188         if self.sbsDiff is not None:
  189             self.sbsDiff.close()
  190         if self.status is not None:
  191             self.status.close()
  192         if self.summary is not None:
  193             self.summary.close()
  194         if self.tagbranchList is not None:
  195             self.tagbranchList.close()
  196         if self.annotate is not None:
  197             self.annotate.close()
  198         if self.serveDlg is not None:
  199             self.serveDlg.close()
  200         
  201         if self.bookmarksListDlg is not None:
  202             self.bookmarksListDlg.close()
  203         if self.bookmarksInOutDlg is not None:
  204             self.bookmarksInOutDlg.close()
  205         
  206         if self.conflictsDlg is not None:
  207             self.conflictsDlg.close()
  208         
  209         if self.bundleFile and os.path.exists(self.bundleFile):
  210             os.remove(self.bundleFile)
  211         
  212         # shut down the project helpers
  213         if self.__projectHelper is not None:
  214             self.__projectHelper.shutdown()
  215         
  216         # shut down the extensions
  217         for extension in self.__extensions.values():
  218             extension.shutdown()
  219         
  220         # shut down the client
  221         self.__client and self.__client.stopServer()
  222     
  223     def initCommand(self, command):
  224         """
  225         Public method to initialize a command arguments list.
  226         
  227         @param command command name (string)
  228         @return list of command options (list of string)
  229         """
  230         args = [command]
  231         self.addArguments(args, self.__plugin.getGlobalOptions())
  232         return args
  233     
  234     def vcsExists(self):
  235         """
  236         Public method used to test for the presence of the hg executable.
  237         
  238         @return flag indicating the existence (boolean) and an error message
  239             (string)
  240         """
  241         from .HgUtilities import hgVersion
  242         
  243         self.versionStr, self.version, errMsg = hgVersion(self.__plugin)
  244         hgExists = errMsg == ""
  245         if hgExists:
  246             self.__getExtensionsInfo()
  247         return hgExists, errMsg
  248     
  249     def vcsInit(self, vcsDir, noDialog=False):
  250         """
  251         Public method used to initialize the mercurial repository.
  252         
  253         The initialization is done, when a project is converted into a
  254         Mercurial controlled project. Therefore we always return TRUE without
  255         doing anything.
  256         
  257         @param vcsDir name of the VCS directory (string)
  258         @param noDialog flag indicating quiet operations (boolean)
  259         @return always TRUE
  260         """
  261         return True
  262     
  263     def vcsConvertProject(self, vcsDataDict, project, addAll=True):
  264         """
  265         Public method to convert an uncontrolled project to a version
  266         controlled project.
  267         
  268         @param vcsDataDict dictionary of data required for the conversion
  269         @type dict
  270         @param project reference to the project object
  271         @type Project
  272         @param addAll flag indicating to add all files to the repository
  273         @type bool
  274         """
  275         success = self.vcsImport(vcsDataDict, project.ppath, addAll=addAll)[0]
  276         if not success:
  277             E5MessageBox.critical(
  278                 self.__ui,
  279                 self.tr("Create project repository"),
  280                 self.tr(
  281                     """The project repository could not be created."""))
  282         else:
  283             pfn = project.pfile
  284             if not os.path.isfile(pfn):
  285                 pfn += "z"
  286             project.closeProject()
  287             project.openProject(pfn)
  288     
  289     def vcsImport(self, vcsDataDict, projectDir, noDialog=False, addAll=True):
  290         """
  291         Public method used to import the project into the Mercurial repository.
  292         
  293         @param vcsDataDict dictionary of data required for the import
  294         @type dict
  295         @param projectDir project directory (string)
  296         @type str
  297         @param noDialog flag indicating quiet operations
  298         @type bool
  299         @param addAll flag indicating to add all files to the repository
  300         @type bool
  301         @return tuple containing a flag indicating an execution without errors
  302             and a flag indicating the version controll status
  303         @rtype tuple of (bool, bool)
  304         """
  305         msg = vcsDataDict["message"]
  306         if not msg:
  307             msg = '***'
  308         
  309         args = self.initCommand("init")
  310         args.append(projectDir)
  311         # init is not possible with the command server
  312         dia = HgDialog(self.tr('Creating Mercurial repository'), self)
  313         res = dia.startProcess(args)
  314         if res:
  315             dia.exec_()
  316         status = dia.normalExit()
  317         
  318         if status:
  319             ignoreName = os.path.join(projectDir, Hg.IgnoreFileName)
  320             if not os.path.exists(ignoreName):
  321                 status = self.hgCreateIgnoreFile(projectDir)
  322             
  323             if status and addAll:
  324                 args = self.initCommand("commit")
  325                 args.append('--addremove')
  326                 args.append('--message')
  327                 args.append(msg)
  328                 dia = HgDialog(
  329                     self.tr('Initial commit to Mercurial repository'),
  330                     self)
  331                 res = dia.startProcess(args, projectDir)
  332                 if res:
  333                     dia.exec_()
  334                 status = dia.normalExit()
  335         
  336         return status, False
  337     
  338     def vcsCheckout(self, vcsDataDict, projectDir, noDialog=False):
  339         """
  340         Public method used to check the project out of a Mercurial repository
  341         (clone).
  342         
  343         @param vcsDataDict dictionary of data required for the checkout
  344         @param projectDir project directory to create (string)
  345         @param noDialog flag indicating quiet operations
  346         @return flag indicating an execution without errors (boolean)
  347         """
  348         noDialog = False
  349         try:
  350             rev = vcsDataDict["revision"]
  351         except KeyError:
  352             rev = None
  353         vcsUrl = self.hgNormalizeURL(vcsDataDict["url"])
  354         
  355         args = self.initCommand("clone")
  356         if rev:
  357             args.append("--rev")
  358             args.append(rev)
  359         if vcsDataDict["largefiles"]:
  360             args.append("--all-largefiles")
  361         args.append(vcsUrl)
  362         args.append(projectDir)
  363         
  364         if noDialog:
  365             out, err = self.__client.runcommand(args)
  366             return err == ""
  367         else:
  368             dia = HgDialog(
  369                 self.tr('Cloning project from a Mercurial repository'),
  370                 self)
  371             res = dia.startProcess(args)
  372             if res:
  373                 dia.exec_()
  374             return dia.normalExit()
  375     
  376     def vcsExport(self, vcsDataDict, projectDir):
  377         """
  378         Public method used to export a directory from the Mercurial repository.
  379         
  380         @param vcsDataDict dictionary of data required for the checkout
  381         @param projectDir project directory to create (string)
  382         @return flag indicating an execution without errors (boolean)
  383         """
  384         status = self.vcsCheckout(vcsDataDict, projectDir)
  385         shutil.rmtree(os.path.join(projectDir, self.adminDir), True)
  386         if os.path.exists(os.path.join(projectDir, Hg.IgnoreFileName)):
  387             os.remove(os.path.join(projectDir, Hg.IgnoreFileName))
  388         return status
  389     
  390     def vcsCommit(self, name, message, noDialog=False, closeBranch=False,
  391                   mq=False, merge=False):
  392         """
  393         Public method used to make the change of a file/directory permanent
  394         in the Mercurial repository.
  395         
  396         @param name file/directory name to be committed (string or list of
  397             strings)
  398         @param message message for this operation (string)
  399         @param noDialog flag indicating quiet operations
  400         @keyparam closeBranch flag indicating a close branch commit (boolean)
  401         @keyparam mq flag indicating a queue commit (boolean)
  402         @keyparam merge flag indicating a merge commit (boolean)
  403         """
  404         msg = message
  405         
  406         if mq or merge:
  407             # ensure dialog is shown for a queue commit
  408             noDialog = False
  409         
  410         if not noDialog:
  411             # call CommitDialog and get message from there
  412             if self.__commitDialog is None:
  413                 from .HgCommitDialog import HgCommitDialog
  414                 self.__commitDialog = HgCommitDialog(self, msg, mq, merge,
  415                                                      self.__ui)
  416                 self.__commitDialog.accepted.connect(self.__vcsCommit_Step2)
  417             self.__commitDialog.show()
  418             self.__commitDialog.raise_()
  419             self.__commitDialog.activateWindow()
  420         
  421         self.__commitData["name"] = name
  422         self.__commitData["msg"] = msg
  423         self.__commitData["noDialog"] = noDialog
  424         self.__commitData["closeBranch"] = closeBranch
  425         self.__commitData["mq"] = mq
  426         self.__commitData["merge"] = merge
  427         
  428         if noDialog:
  429             self.__vcsCommit_Step2()
  430     
  431     def __vcsCommit_Step2(self):
  432         """
  433         Private slot performing the second step of the commit action.
  434         """
  435         name = self.__commitData["name"]
  436         msg = self.__commitData["msg"]
  437         noDialog = self.__commitData["noDialog"]
  438         closeBranch = self.__commitData["closeBranch"]
  439         mq = self.__commitData["mq"]
  440         merge = self.__commitData["merge"]
  441         
  442         if not noDialog:
  443             # check, if there are unsaved changes, that should be committed
  444             if isinstance(name, list):
  445                 nameList = name
  446             else:
  447                 nameList = [name]
  448             ok = True
  449             for nam in nameList:
  450                 # check for commit of the project
  451                 if os.path.isdir(nam):
  452                     project = e5App().getObject("Project")
  453                     if nam == project.getProjectPath():
  454                         ok &= (
  455                             project.checkAllScriptsDirty(
  456                                 reportSyntaxErrors=True) and
  457                             project.checkDirty()
  458                         )
  459                         continue
  460                 elif os.path.isfile(nam):
  461                     editor = (
  462                         e5App().getObject("ViewManager").getOpenEditor(nam)
  463                     )
  464                     if editor:
  465                         ok &= editor.checkDirty()
  466                 if not ok:
  467                     break
  468             
  469             if not ok:
  470                 res = E5MessageBox.yesNo(
  471                     self.__ui,
  472                     self.tr("Commit Changes"),
  473                     self.tr(
  474                         """The commit affects files, that have unsaved"""
  475                         """ changes. Shall the commit be continued?"""),
  476                     icon=E5MessageBox.Warning)
  477                 if not res:
  478                     return
  479         
  480         if isinstance(name, list):
  481             dname, fnames = self.splitPathList(name)
  482         else:
  483             dname, fname = self.splitPath(name)
  484         
  485         # find the root of the repo
  486         repodir = dname
  487         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
  488             repodir = os.path.dirname(repodir)
  489             if os.path.splitdrive(repodir)[1] == os.sep:
  490                 return
  491         
  492         if self.__commitDialog is not None:
  493             (msg, amend, commitSubrepositories, author,
  494              dateTime) = self.__commitDialog.getCommitData()
  495             self.__commitDialog.deleteLater()
  496             self.__commitDialog = None
  497             if amend and not msg:
  498                 msg = self.__getMostRecentCommitMessage(repodir)
  499         else:
  500             amend = False
  501             commitSubrepositories = False
  502             author = ""
  503             dateTime = ""
  504         
  505         if not msg and not amend:
  506             msg = '***'
  507         
  508         args = self.initCommand("commit")
  509         args.append("-v")
  510         if mq:
  511             args.append("--mq")
  512         elif merge:
  513             if author:
  514                 args.append("--user")
  515                 args.append(author)
  516             if dateTime:
  517                 args.append("--date")
  518                 args.append(dateTime)
  519         else:
  520             if closeBranch:
  521                 args.append("--close-branch")
  522             if amend:
  523                 args.append("--amend")
  524             if commitSubrepositories:
  525                 args.append("--subrepos")
  526             if author:
  527                 args.append("--user")
  528                 args.append(author)
  529             if dateTime:
  530                 args.append("--date")
  531                 args.append(dateTime)
  532         if msg:
  533             args.append("--message")
  534             args.append(msg)
  535         if isinstance(name, list):
  536             self.addArguments(args, name)
  537         else:
  538             if dname != repodir or fname != ".":
  539                 args.append(name)
  540         
  541         dia = HgDialog(
  542             self.tr('Committing changes to Mercurial repository'),
  543             self)
  544         res = dia.startProcess(args, dname)
  545         if res:
  546             dia.exec_()
  547         self.committed.emit()
  548         if self.__forgotNames:
  549             model = e5App().getObject("Project").getModel()
  550             for name in self.__forgotNames:
  551                 model.updateVCSStatus(name)
  552             self.__forgotNames = []
  553         self.checkVCSStatus()
  554     
  555     def __getMostRecentCommitMessage(self, repodir):
  556         """
  557         Private method to get the most recent commit message.
  558         
  559         Note: This message is extracted from the parent commit of the
  560         working directory.
  561         
  562         @param repodir path containing the repository
  563         @type str
  564         @return most recent commit message
  565         @rtype str
  566         """
  567         args = self.initCommand("log")
  568         args.append("--rev")
  569         args.append(".")
  570         args.append('--template')
  571         args.append('{desc}')
  572         
  573         output, error = self.__client.runcommand(args)
  574         
  575         return output
  576     
  577     def vcsUpdate(self, name, noDialog=False, revision=None):
  578         """
  579         Public method used to update a file/directory with the Mercurial
  580         repository.
  581         
  582         @param name file/directory name to be updated (string or list of
  583             strings)
  584         @param noDialog flag indicating quiet operations (boolean)
  585         @keyparam revision revision to update to (string)
  586         @return flag indicating, that the update contained an add
  587             or delete (boolean)
  588         """
  589         args = self.initCommand("update")
  590         if "-v" not in args and "--verbose" not in args:
  591             args.append("-v")
  592         if revision:
  593             args.append("-r")
  594             args.append(revision)
  595         
  596         if isinstance(name, list):
  597             dname, fnames = self.splitPathList(name)
  598         else:
  599             dname, fname = self.splitPath(name)
  600         
  601         # find the root of the repo
  602         repodir = dname
  603         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
  604             repodir = os.path.dirname(repodir)
  605             if os.path.splitdrive(repodir)[1] == os.sep:
  606                 return False
  607         
  608         if noDialog:
  609             out, err = self.__client.runcommand(args)
  610             res = False
  611         else:
  612             dia = HgDialog(self.tr(
  613                 'Synchronizing with the Mercurial repository'),
  614                 self)
  615             res = dia.startProcess(args, repodir)
  616             if res:
  617                 dia.exec_()
  618                 res = dia.hasAddOrDelete()
  619         self.checkVCSStatus()
  620         return res
  621     
  622     def vcsAdd(self, name, isDir=False, noDialog=False):
  623         """
  624         Public method used to add a file/directory to the Mercurial repository.
  625         
  626         @param name file/directory name to be added (string)
  627         @param isDir flag indicating name is a directory (boolean)
  628         @param noDialog flag indicating quiet operations
  629         """
  630         args = self.initCommand("add")
  631         args.append("-v")
  632         
  633         if isinstance(name, list):
  634             if isDir:
  635                 dname, fname = os.path.split(name[0])
  636             else:
  637                 dname, fnames = self.splitPathList(name)
  638         else:
  639             if isDir:
  640                 dname, fname = os.path.split(name)
  641             else:
  642                 dname, fname = self.splitPath(name)
  643         
  644         # find the root of the repo
  645         repodir = dname
  646         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
  647             repodir = os.path.dirname(repodir)
  648             if os.path.splitdrive(repodir)[1] == os.sep:
  649                 return
  650         
  651         if isinstance(name, list):
  652             self.addArguments(args, name)
  653         else:
  654             args.append(name)
  655         
  656         if noDialog:
  657             out, err = self.__client.runcommand(args)
  658         else:
  659             dia = HgDialog(
  660                 self.tr(
  661                     'Adding files/directories to the Mercurial repository'),
  662                 self)
  663             res = dia.startProcess(args, repodir)
  664             if res:
  665                 dia.exec_()
  666     
  667     def vcsAddBinary(self, name, isDir=False):
  668         """
  669         Public method used to add a file/directory in binary mode to the
  670         Mercurial repository.
  671         
  672         @param name file/directory name to be added (string)
  673         @param isDir flag indicating name is a directory (boolean)
  674         """
  675         self.vcsAdd(name, isDir)
  676     
  677     def vcsAddTree(self, path):
  678         """
  679         Public method to add a directory tree rooted at path to the Mercurial
  680         repository.
  681         
  682         @param path root directory of the tree to be added (string or list of
  683             strings))
  684         """
  685         self.vcsAdd(path, isDir=False)
  686     
  687     def vcsRemove(self, name, project=False, noDialog=False):
  688         """
  689         Public method used to remove a file/directory from the Mercurial
  690         repository.
  691         
  692         The default operation is to remove the local copy as well.
  693         
  694         @param name file/directory name to be removed (string or list of
  695             strings))
  696         @param project flag indicating deletion of a project tree (boolean)
  697             (not needed)
  698         @param noDialog flag indicating quiet operations
  699         @return flag indicating successfull operation (boolean)
  700         """
  701         args = self.initCommand("remove")
  702         args.append("-v")
  703         if noDialog and '--force' not in args:
  704             args.append('--force')
  705         
  706         if isinstance(name, list):
  707             dname, fnames = self.splitPathList(name)
  708             self.addArguments(args, name)
  709         else:
  710             dname, fname = self.splitPath(name)
  711             args.append(name)
  712         
  713         # find the root of the repo
  714         repodir = dname
  715         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
  716             repodir = os.path.dirname(repodir)
  717             if os.path.splitdrive(repodir)[1] == os.sep:
  718                 return False
  719         
  720         if noDialog:
  721             out, err = self.__client.runcommand(args)
  722             res = err == ""
  723         else:
  724             dia = HgDialog(
  725                 self.tr(
  726                     'Removing files/directories from the Mercurial'
  727                     ' repository'),
  728                 self)
  729             res = dia.startProcess(args, repodir)
  730             if res:
  731                 dia.exec_()
  732                 res = dia.normalExitWithoutErrors()
  733         
  734         return res
  735     
  736     def vcsMove(self, name, project, target=None, noDialog=False):
  737         """
  738         Public method used to move a file/directory.
  739         
  740         @param name file/directory name to be moved (string)
  741         @param project reference to the project object
  742         @param target new name of the file/directory (string)
  743         @param noDialog flag indicating quiet operations
  744         @return flag indicating successfull operation (boolean)
  745         """
  746         isDir = os.path.isdir(name)
  747         
  748         res = False
  749         if noDialog:
  750             if target is None:
  751                 return False
  752             force = True
  753             accepted = True
  754         else:
  755             from .HgCopyDialog import HgCopyDialog
  756             dlg = HgCopyDialog(name, None, True)
  757             accepted = dlg.exec_() == QDialog.Accepted
  758             if accepted:
  759                 target, force = dlg.getData()
  760         
  761         if accepted:
  762             args = self.initCommand("rename")
  763             args.append("-v")
  764             if force:
  765                 args.append('--force')
  766             args.append(name)
  767             args.append(target)
  768             
  769             dname, fname = self.splitPath(name)
  770             # find the root of the repo
  771             repodir = dname
  772             while not os.path.isdir(os.path.join(repodir, self.adminDir)):
  773                 repodir = os.path.dirname(repodir)
  774                 if os.path.splitdrive(repodir)[1] == os.sep:
  775                     return False
  776             
  777             if noDialog:
  778                 out, err = self.__client.runcommand(args)
  779                 res = err == ""
  780             else:
  781                 dia = HgDialog(self.tr('Renaming {0}').format(name), self)
  782                 res = dia.startProcess(args, repodir)
  783                 if res:
  784                     dia.exec_()
  785                     res = dia.normalExit()
  786             if res:
  787                 if target.startswith(project.getProjectPath()):
  788                     if isDir:
  789                         project.moveDirectory(name, target)
  790                     else:
  791                         project.renameFileInPdata(name, target)
  792                 else:
  793                     if isDir:
  794                         project.removeDirectory(name)
  795                     else:
  796                         project.removeFile(name)
  797         return res
  798     
  799     def vcsDiff(self, name):
  800         """
  801         Public method used to view the difference of a file/directory to the
  802         Mercurial repository.
  803         
  804         If name is a directory and is the project directory, all project files
  805         are saved first. If name is a file (or list of files), which is/are
  806         being edited and has unsaved modification, they can be saved or the
  807         operation may be aborted.
  808         
  809         @param name file/directory name to be diffed (string)
  810         """
  811         if isinstance(name, list):
  812             names = name[:]
  813         else:
  814             names = [name]
  815         for nam in names:
  816             if os.path.isfile(nam):
  817                 editor = e5App().getObject("ViewManager").getOpenEditor(nam)
  818                 if editor and not editor.checkDirty():
  819                     return
  820             else:
  821                 project = e5App().getObject("Project")
  822                 if nam == project.ppath and not project.saveAllScripts():
  823                     return
  824         if self.diff is None:
  825             from .HgDiffDialog import HgDiffDialog
  826             self.diff = HgDiffDialog(self)
  827         self.diff.show()
  828         self.diff.raise_()
  829         QApplication.processEvents()
  830         self.diff.start(name, refreshable=True)
  831     
  832     def vcsStatus(self, name):
  833         """
  834         Public method used to view the status of files/directories in the
  835         Mercurial repository.
  836         
  837         @param name file/directory name(s) to show the status of
  838             (string or list of strings)
  839         """
  840         if self.status is None:
  841             from .HgStatusDialog import HgStatusDialog
  842             self.status = HgStatusDialog(self)
  843         self.status.show()
  844         self.status.raise_()
  845         self.status.start(name)
  846     
  847     def hgSummary(self, mq=False, largefiles=False):
  848         """
  849         Public method used to show some summary information of the
  850         working directory state.
  851         
  852         @param mq flag indicating to show the queue status as well (boolean)
  853         @param largefiles flag indicating to show the largefiles status as
  854             well (boolean)
  855         """
  856         if self.summary is None:
  857             from .HgSummaryDialog import HgSummaryDialog
  858             self.summary = HgSummaryDialog(self)
  859         self.summary.show()
  860         self.summary.raise_()
  861         self.summary.start(self.__projectHelper.getProject().getProjectPath(),
  862                            mq=mq, largefiles=largefiles)
  863     
  864     def vcsTag(self, name, revision=None, tagName=None):
  865         """
  866         Public method used to set/remove a tag in the Mercurial repository.
  867         
  868         @param name file/directory name to determine the repo root from
  869             (string)
  870         @param revision revision to set tag for (string)
  871         @param tagName name of the tag (string)
  872         @return flag indicating a performed tag action (boolean)
  873         """
  874         dname, fname = self.splitPath(name)
  875         
  876         # find the root of the repo
  877         repodir = dname
  878         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
  879             repodir = os.path.dirname(repodir)
  880             if os.path.splitdrive(repodir)[1] == os.sep:
  881                 return False
  882         
  883         from .HgTagDialog import HgTagDialog
  884         dlg = HgTagDialog(self.hgGetTagsList(repodir, withType=True),
  885                           revision, tagName)
  886         if dlg.exec_() == QDialog.Accepted:
  887             tag, revision, tagOp, force = dlg.getParameters()
  888         else:
  889             return False
  890         
  891         args = self.initCommand("tag")
  892         msgPart = ""
  893         if tagOp in [HgTagDialog.CreateLocalTag, HgTagDialog.DeleteLocalTag]:
  894             args.append('--local')
  895             msgPart = "local "
  896         else:
  897             msgPart = "global "
  898         if tagOp in [HgTagDialog.DeleteGlobalTag, HgTagDialog.DeleteLocalTag]:
  899             args.append('--remove')
  900         if tagOp in [HgTagDialog.CreateGlobalTag, HgTagDialog.CreateLocalTag]:
  901             if revision:
  902                 args.append("--rev")
  903                 args.append(revision)
  904         if force:
  905             args.append("--force")
  906         args.append('--message')
  907         if tagOp in [HgTagDialog.CreateGlobalTag, HgTagDialog.CreateLocalTag]:
  908             tag = tag.strip().replace(" ", "_")
  909             args.append("Created {1}tag <{0}>.".format(tag, msgPart))
  910         else:
  911             args.append("Removed {1}tag <{0}>.".format(tag, msgPart))
  912         args.append(tag)
  913         
  914         dia = HgDialog(self.tr('Tagging in the Mercurial repository'),
  915                        self)
  916         res = dia.startProcess(args, repodir)
  917         if res:
  918             dia.exec_()
  919         
  920         return True
  921     
  922     def hgRevert(self, name):
  923         """
  924         Public method used to revert changes made to a file/directory.
  925         
  926         @param name file/directory name to be reverted (string)
  927         @return flag indicating, that the update contained an add
  928             or delete (boolean)
  929         """
  930         args = self.initCommand("revert")
  931         if not self.getPlugin().getPreferences("CreateBackup"):
  932             args.append("--no-backup")
  933         args.append("-v")
  934         if isinstance(name, list):
  935             dname, fnames = self.splitPathList(name)
  936             self.addArguments(args, name)
  937             names = name[:]
  938         else:
  939             dname, fname = self.splitPath(name)
  940             args.append(name)
  941             names = [name]
  942         
  943         # find the root of the repo
  944         repodir = dname
  945         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
  946             repodir = os.path.dirname(repodir)
  947             if os.path.splitdrive(repodir)[1] == os.sep:
  948                 return False
  949         
  950         project = e5App().getObject("Project")
  951         names = [project.getRelativePath(nam) for nam in names]
  952         if names[0]:
  953             from UI.DeleteFilesConfirmationDialog import (
  954                 DeleteFilesConfirmationDialog
  955             )
  956             dlg = DeleteFilesConfirmationDialog(
  957                 self.parent(),
  958                 self.tr("Revert changes"),
  959                 self.tr(
  960                     "Do you really want to revert all changes to these files"
  961                     " or directories?"),
  962                 names)
  963             yes = dlg.exec_() == QDialog.Accepted
  964         else:
  965             yes = E5MessageBox.yesNo(
  966                 None,
  967                 self.tr("Revert changes"),
  968                 self.tr("""Do you really want to revert all changes of"""
  969                         """ the project?"""))
  970         if yes:
  971             dia = HgDialog(self.tr('Reverting changes'), self)
  972             res = dia.startProcess(args, repodir)
  973             if res:
  974                 dia.exec_()
  975                 res = dia.hasAddOrDelete()
  976             self.checkVCSStatus()
  977         else:
  978             res = False
  979         
  980         return res
  981     
  982     def vcsMerge(self, name, rev=""):
  983         """
  984         Public method used to merge a URL/revision into the local project.
  985         
  986         @param name file/directory name to be merged
  987         @type str
  988         @keyparam rev revision to merge with
  989         @type str
  990         """
  991         dname, fname = self.splitPath(name)
  992         
  993         # find the root of the repo
  994         repodir = dname
  995         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
  996             repodir = os.path.dirname(repodir)
  997             if os.path.splitdrive(repodir)[1] == os.sep:
  998                 return
  999         
 1000         if not rev:
 1001             from .HgMergeDialog import HgMergeDialog
 1002             dlg = HgMergeDialog(self.hgGetTagsList(repodir),
 1003                                 self.hgGetBranchesList(repodir),
 1004                                 self.hgGetBookmarksList(repodir))
 1005             if dlg.exec_() == QDialog.Accepted:
 1006                 rev, force = dlg.getParameters()
 1007             else:
 1008                 return
 1009         else:
 1010             force = False
 1011         
 1012         args = self.initCommand("merge")
 1013         if force:
 1014             args.append("--force")
 1015         if self.getPlugin().getPreferences("InternalMerge"):
 1016             args.append("--tool")
 1017             args.append("internal:merge")
 1018         if rev:
 1019             args.append("--rev")
 1020             args.append(rev)
 1021         
 1022         dia = HgDialog(self.tr('Merging').format(name), self)
 1023         res = dia.startProcess(args, repodir)
 1024         if res:
 1025             dia.exec_()
 1026         self.checkVCSStatus()
 1027     
 1028     def hgReMerge(self, name):
 1029         """
 1030         Public method used to merge a URL/revision into the local project.
 1031         
 1032         @param name file/directory name to be merged (string)
 1033         """
 1034         args = self.initCommand("resolve")
 1035         if self.getPlugin().getPreferences("InternalMerge"):
 1036             args.append("--tool")
 1037             args.append("internal:merge")
 1038         if isinstance(name, list):
 1039             dname, fnames = self.splitPathList(name)
 1040             self.addArguments(args, name)
 1041             names = name[:]
 1042         else:
 1043             dname, fname = self.splitPath(name)
 1044             args.append(name)
 1045             names = [name]
 1046         
 1047         # find the root of the repo
 1048         repodir = dname
 1049         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 1050             repodir = os.path.dirname(repodir)
 1051             if os.path.splitdrive(repodir)[1] == os.sep:
 1052                 return
 1053         
 1054         project = e5App().getObject("Project")
 1055         names = [project.getRelativePath(nam) for nam in names]
 1056         if names[0]:
 1057             from UI.DeleteFilesConfirmationDialog import (
 1058                 DeleteFilesConfirmationDialog
 1059             )
 1060             dlg = DeleteFilesConfirmationDialog(
 1061                 self.parent(),
 1062                 self.tr("Re-Merge"),
 1063                 self.tr(
 1064                     "Do you really want to re-merge these files"
 1065                     " or directories?"),
 1066                 names)
 1067             yes = dlg.exec_() == QDialog.Accepted
 1068         else:
 1069             yes = E5MessageBox.yesNo(
 1070                 None,
 1071                 self.tr("Re-Merge"),
 1072                 self.tr("""Do you really want to re-merge the project?"""))
 1073         if yes:
 1074             dia = HgDialog(self.tr('Re-Merging').format(name), self)
 1075             res = dia.startProcess(args, repodir)
 1076             if res:
 1077                 dia.exec_()
 1078             self.checkVCSStatus()
 1079     
 1080     def vcsSwitch(self, name):
 1081         """
 1082         Public method used to switch a working directory to a different
 1083         revision.
 1084         
 1085         @param name directory name to be switched (string)
 1086         @return flag indicating, that the switch contained an add
 1087             or delete (boolean)
 1088         """
 1089         dname, fname = self.splitPath(name)
 1090         
 1091         # find the root of the repo
 1092         repodir = dname
 1093         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 1094             repodir = os.path.dirname(repodir)
 1095             if os.path.splitdrive(repodir)[1] == os.sep:
 1096                 return False
 1097         
 1098         from .HgRevisionSelectionDialog import HgRevisionSelectionDialog
 1099         dlg = HgRevisionSelectionDialog(self.hgGetTagsList(repodir),
 1100                                         self.hgGetBranchesList(repodir),
 1101                                         self.hgGetBookmarksList(repodir),
 1102                                         self.tr("Current branch tip"))
 1103         if dlg.exec_() == QDialog.Accepted:
 1104             rev = dlg.getRevision()
 1105             return self.vcsUpdate(name, revision=rev)
 1106         
 1107         return False
 1108 
 1109     def vcsRegisteredState(self, name):
 1110         """
 1111         Public method used to get the registered state of a file in the vcs.
 1112         
 1113         @param name filename to check (string)
 1114         @return a combination of canBeCommited and canBeAdded
 1115         """
 1116         if name.endswith(os.sep):
 1117             name = name[:-1]
 1118         name = os.path.normcase(name)
 1119         dname, fname = self.splitPath(name)
 1120         
 1121         if fname == '.' and os.path.isdir(os.path.join(dname, self.adminDir)):
 1122             return self.canBeCommitted
 1123         
 1124         if name in self.statusCache:
 1125             return self.statusCache[name]
 1126         
 1127         # find the root of the repo
 1128         repodir = dname
 1129         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 1130             repodir = os.path.dirname(repodir)
 1131             if os.path.splitdrive(repodir)[1] == os.sep:
 1132                 return 0
 1133         
 1134         args = self.initCommand("status")
 1135         args.append('--all')
 1136         args.append('--noninteractive')
 1137         
 1138         output, error = self.__client.runcommand(args)
 1139         
 1140         if output:
 1141             for line in output.splitlines():
 1142                 if len(line) > 2 and line[0] in "MARC!?I" and line[1] == " ":
 1143                     flag, path = line.split(" ", 1)
 1144                     absname = os.path.join(repodir, os.path.normcase(path))
 1145                     if flag not in "?I":
 1146                         if fname == '.':
 1147                             if absname.startswith(dname + os.path.sep):
 1148                                 return self.canBeCommitted
 1149                             if absname == dname:
 1150                                 return self.canBeCommitted
 1151                         else:
 1152                             if absname == name:
 1153                                 return self.canBeCommitted
 1154         
 1155         return self.canBeAdded
 1156     
 1157     def vcsAllRegisteredStates(self, names, dname, shortcut=True):
 1158         """
 1159         Public method used to get the registered states of a number of files
 1160         in the vcs.
 1161         
 1162         <b>Note:</b> If a shortcut is to be taken, the code will only check,
 1163         if the named directory has been scanned already. If so, it is assumed,
 1164         that the states for all files have been populated by the previous run.
 1165         
 1166         @param names dictionary with all filenames to be checked as keys
 1167         @param dname directory to check in (string)
 1168         @param shortcut flag indicating a shortcut should be taken (boolean)
 1169         @return the received dictionary completed with a combination of
 1170             canBeCommited and canBeAdded or None in order to signal an error
 1171         """
 1172         if dname.endswith(os.sep):
 1173             dname = dname[:-1]
 1174         dname = os.path.normcase(dname)
 1175         
 1176         found = False
 1177         for name in list(self.statusCache.keys()):
 1178             if name in names:
 1179                 found = True
 1180                 names[name] = self.statusCache[name]
 1181         
 1182         if not found:
 1183             # find the root of the repo
 1184             repodir = dname
 1185             while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 1186                 repodir = os.path.dirname(repodir)
 1187                 if os.path.splitdrive(repodir)[1] == os.sep:
 1188                     return names
 1189         
 1190             args = self.initCommand("status")
 1191             args.append('--all')
 1192             args.append('--noninteractive')
 1193             
 1194             output, error = self.__client.runcommand(args)
 1195             
 1196             if output:
 1197                 dirs = [x for x in names.keys() if os.path.isdir(x)]
 1198                 for line in output.splitlines():
 1199                     if line and line[0] in "MARC!?I":
 1200                         flag, path = line.split(" ", 1)
 1201                         name = os.path.normcase(os.path.join(repodir, path))
 1202                         dirName = os.path.dirname(name)
 1203                         if name.startswith(dname):
 1204                             if flag not in "?I":
 1205                                 if name in names:
 1206                                     names[name] = self.canBeCommitted
 1207                                 if dirName in names:
 1208                                     names[dirName] = self.canBeCommitted
 1209                                 if dirs:
 1210                                     for d in dirs:
 1211                                         if name.startswith(d):
 1212                                             names[d] = self.canBeCommitted
 1213                                             dirs.remove(d)
 1214                                             break
 1215                         if flag not in "?I":
 1216                             self.statusCache[name] = self.canBeCommitted
 1217                             self.statusCache[dirName] = self.canBeCommitted
 1218                         else:
 1219                             self.statusCache[name] = self.canBeAdded
 1220                             if dirName not in self.statusCache:
 1221                                 self.statusCache[dirName] = self.canBeAdded
 1222         
 1223         return names
 1224     
 1225     def clearStatusCache(self):
 1226         """
 1227         Public method to clear the status cache.
 1228         """
 1229         self.statusCache = {}
 1230     
 1231     def vcsName(self):
 1232         """
 1233         Public method returning the name of the vcs.
 1234         
 1235         @return always 'Mercurial' (string)
 1236         """
 1237         return "Mercurial"
 1238     
 1239     def vcsInitConfig(self, project):
 1240         """
 1241         Public method to initialize the VCS configuration.
 1242         
 1243         This method ensures, that an ignore file exists.
 1244         
 1245         @param project reference to the project (Project)
 1246         """
 1247         ppath = project.getProjectPath()
 1248         if ppath:
 1249             ignoreName = os.path.join(ppath, Hg.IgnoreFileName)
 1250             if not os.path.exists(ignoreName):
 1251                 self.hgCreateIgnoreFile(project.getProjectPath(), autoAdd=True)
 1252     
 1253     def vcsCleanup(self, name):
 1254         """
 1255         Public method used to cleanup the working directory.
 1256         
 1257         @param name directory name to be cleaned up (string)
 1258         """
 1259         patterns = self.getPlugin().getPreferences("CleanupPatterns").split()
 1260         
 1261         entries = []
 1262         for pat in patterns:
 1263             entries.extend(Utilities.direntries(name, True, pat))
 1264         
 1265         for entry in entries:
 1266             try:
 1267                 os.remove(entry)
 1268             except OSError:
 1269                 pass
 1270     
 1271     def vcsCommandLine(self, name):
 1272         """
 1273         Public method used to execute arbitrary mercurial commands.
 1274         
 1275         @param name directory name of the working directory (string)
 1276         """
 1277         from .HgCommandDialog import HgCommandDialog
 1278         dlg = HgCommandDialog(self.commandHistory, name)
 1279         if dlg.exec_() == QDialog.Accepted:
 1280             command = dlg.getData()
 1281             commandList = Utilities.parseOptionString(command)
 1282             
 1283             # This moves any previous occurrence of these arguments to the head
 1284             # of the list.
 1285             if command in self.commandHistory:
 1286                 self.commandHistory.remove(command)
 1287             self.commandHistory.insert(0, command)
 1288             
 1289             args = []
 1290             self.addArguments(args, commandList)
 1291             
 1292             # find the root of the repo
 1293             repodir = name
 1294             while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 1295                 repodir = os.path.dirname(repodir)
 1296                 if os.path.splitdrive(repodir)[1] == os.sep:
 1297                     return
 1298             
 1299             dia = HgDialog(self.tr('Mercurial command'), self)
 1300             res = dia.startProcess(args, repodir)
 1301             if res:
 1302                 dia.exec_()
 1303     
 1304     def vcsOptionsDialog(self, project, archive, editable=False, parent=None):
 1305         """
 1306         Public method to get a dialog to enter repository info.
 1307         
 1308         @param project reference to the project object
 1309         @param archive name of the project in the repository (string)
 1310         @param editable flag indicating that the project name is editable
 1311             (boolean)
 1312         @param parent parent widget (QWidget)
 1313         @return reference to the instantiated options dialog (HgOptionsDialog)
 1314         """
 1315         from .HgOptionsDialog import HgOptionsDialog
 1316         return HgOptionsDialog(self, project, parent)
 1317     
 1318     def vcsNewProjectOptionsDialog(self, parent=None):
 1319         """
 1320         Public method to get a dialog to enter repository info for getting a
 1321         new project.
 1322         
 1323         @param parent parent widget (QWidget)
 1324         @return reference to the instantiated options dialog
 1325             (HgNewProjectOptionsDialog)
 1326         """
 1327         from .HgNewProjectOptionsDialog import HgNewProjectOptionsDialog
 1328         return HgNewProjectOptionsDialog(self, parent)
 1329     
 1330     def vcsRepositoryInfos(self, ppath):
 1331         """
 1332         Public method to retrieve information about the repository.
 1333         
 1334         @param ppath local path to get the repository infos (string)
 1335         @return string with ready formated info for display (string)
 1336         """
 1337         args = self.initCommand("parents")
 1338         args.append('--template')
 1339         args.append('{rev}:{node|short}@@@{tags}@@@{author|xmlescape}@@@'
 1340                     '{date|isodate}@@@{branches}@@@{bookmarks}\n')
 1341         
 1342         output, error = self.__client.runcommand(args)
 1343         
 1344         infoBlock = []
 1345         if output:
 1346             index = 0
 1347             for line in output.splitlines():
 1348                 index += 1
 1349                 (changeset, tags, author, date, branches,
 1350                  bookmarks) = line.split("@@@")
 1351                 cdate, ctime = date.split()[:2]
 1352                 info = []
 1353                 info.append(QCoreApplication.translate(
 1354                     "mercurial",
 1355                     """<tr><td><b>Parent #{0}</b></td><td></td></tr>\n"""
 1356                     """<tr><td><b>Changeset</b></td><td>{1}</td></tr>""")
 1357                     .format(index, changeset))
 1358                 if tags:
 1359                     info.append(QCoreApplication.translate(
 1360                         "mercurial",
 1361                         """<tr><td><b>Tags</b></td><td>{0}</td></tr>""")
 1362                         .format('<br/>'.join(tags.split())))
 1363                 if bookmarks:
 1364                     info.append(QCoreApplication.translate(
 1365                         "mercurial",
 1366                         """<tr><td><b>Bookmarks</b></td><td>{0}</td></tr>""")
 1367                         .format('<br/>'.join(bookmarks.split())))
 1368                 if branches:
 1369                     info.append(QCoreApplication.translate(
 1370                         "mercurial",
 1371                         """<tr><td><b>Branches</b></td><td>{0}</td></tr>""")
 1372                         .format('<br/>'.join(branches.split())))
 1373                 info.append(QCoreApplication.translate(
 1374                     "mercurial",
 1375                     """<tr><td><b>Last author</b></td><td>{0}</td></tr>\n"""
 1376                     """<tr><td><b>Committed date</b></td><td>{1}</td></tr>\n"""
 1377                     """<tr><td><b>Committed time</b></td><td>{2}</td></tr>""")
 1378                     .format(author, cdate, ctime))
 1379                 infoBlock.append("\n".join(info))
 1380         if infoBlock:
 1381             infoStr = """<tr></tr>{0}""".format("<tr></tr>".join(infoBlock))
 1382         else:
 1383             infoStr = ""
 1384         
 1385         url = ""
 1386         args = self.initCommand("showconfig")
 1387         args.append('paths.default')
 1388         
 1389         output, error = self.__client.runcommand(args)
 1390         
 1391         if output:
 1392             url = output.splitlines()[0].strip()
 1393         else:
 1394             url = ""
 1395         
 1396         return QCoreApplication.translate(
 1397             'mercurial',
 1398             """<h3>Repository information</h3>\n"""
 1399             """<p><table>\n"""
 1400             """<tr><td><b>Mercurial V.</b></td><td>{0}</td></tr>\n"""
 1401             """<tr></tr>\n"""
 1402             """<tr><td><b>URL</b></td><td>{1}</td></tr>\n"""
 1403             """{2}"""
 1404             """</table></p>\n"""
 1405         ).format(self.versionStr, url, infoStr)
 1406     
 1407     def vcsSupportCommandOptions(self):
 1408         """
 1409         Public method to signal the support of user settable command options.
 1410         
 1411         @return flag indicating the support  of user settable command options
 1412             (boolean)
 1413         """
 1414         return False
 1415     
 1416     ###########################################################################
 1417     ## Private Mercurial specific methods are below.
 1418     ###########################################################################
 1419     
 1420     def hgNormalizeURL(self, url):
 1421         """
 1422         Public method to normalize a url for Mercurial.
 1423         
 1424         @param url url string (string)
 1425         @return properly normalized url for mercurial (string)
 1426         """
 1427         url = url.replace('\\', '/')
 1428         if url.endswith('/'):
 1429             url = url[:-1]
 1430         urll = url.split('//')
 1431         return "{0}//{1}".format(urll[0], '/'.join(urll[1:]))
 1432     
 1433     def hgCopy(self, name, project):
 1434         """
 1435         Public method used to copy a file/directory.
 1436         
 1437         @param name file/directory name to be copied (string)
 1438         @param project reference to the project object
 1439         @return flag indicating successful operation (boolean)
 1440         """
 1441         from .HgCopyDialog import HgCopyDialog
 1442         dlg = HgCopyDialog(name)
 1443         res = False
 1444         if dlg.exec_() == QDialog.Accepted:
 1445             target, force = dlg.getData()
 1446             
 1447             args = self.initCommand("copy")
 1448             args.append("-v")
 1449             args.append(name)
 1450             args.append(target)
 1451             
 1452             dname, fname = self.splitPath(name)
 1453             # find the root of the repo
 1454             repodir = dname
 1455             while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 1456                 repodir = os.path.dirname(repodir)
 1457                 if os.path.splitdrive(repodir)[1] == os.sep:
 1458                     return False
 1459             
 1460             dia = HgDialog(
 1461                 self.tr('Copying {0}').format(name), self)
 1462             res = dia.startProcess(args, repodir)
 1463             if res:
 1464                 dia.exec_()
 1465                 res = dia.normalExit()
 1466                 if (
 1467                     res and
 1468                     target.startswith(project.getProjectPath())
 1469                 ):
 1470                     if os.path.isdir(name):
 1471                         project.copyDirectory(name, target)
 1472                     else:
 1473                         project.appendFile(target)
 1474         return res
 1475     
 1476     def hgGetTagsList(self, repodir, withType=False):
 1477         """
 1478         Public method to get the list of tags.
 1479         
 1480         @param repodir directory name of the repository (string)
 1481         @param withType flag indicating to get the tag type as well (boolean)
 1482         @return list of tags (list of string) or list of tuples of
 1483             tag name and flag indicating a local tag (list of tuple of string
 1484             and boolean), if withType is True
 1485         """
 1486         args = self.initCommand("tags")
 1487         args.append('--verbose')
 1488         
 1489         output, error = self.__client.runcommand(args)
 1490         
 1491         tagsList = []
 1492         if output:
 1493             for line in output.splitlines():
 1494                 li = line.strip().split()
 1495                 if li[-1][0] in "1234567890":
 1496                     # last element is a rev:changeset
 1497                     del li[-1]
 1498                     isLocal = False
 1499                 else:
 1500                     del li[-2:]
 1501                     isLocal = True
 1502                 name = " ".join(li)
 1503                 if name not in ["tip", "default"]:
 1504                     if withType:
 1505                         tagsList.append((name, isLocal))
 1506                     else:
 1507                         tagsList.append(name)
 1508         
 1509         if withType:
 1510             return tagsList
 1511         else:
 1512             if tagsList:
 1513                 self.tagsList = tagsList
 1514             return self.tagsList[:]
 1515     
 1516     def hgGetBranchesList(self, repodir):
 1517         """
 1518         Public method to get the list of branches.
 1519         
 1520         @param repodir directory name of the repository (string)
 1521         @return list of branches (list of string)
 1522         """
 1523         args = self.initCommand("branches")
 1524         args.append('--closed')
 1525         
 1526         output, error = self.__client.runcommand(args)
 1527         
 1528         if output:
 1529             self.branchesList = []
 1530             for line in output.splitlines():
 1531                 li = line.strip().split()
 1532                 if li[-1][0] in "1234567890":
 1533                     # last element is a rev:changeset
 1534                     del li[-1]
 1535                 else:
 1536                     del li[-2:]
 1537                 name = " ".join(li)
 1538                 if name not in ["tip", "default"]:
 1539                     self.branchesList.append(name)
 1540         
 1541         return self.branchesList[:]
 1542     
 1543     def hgListTagBranch(self, path, tags=True):
 1544         """
 1545         Public method used to list the available tags or branches.
 1546         
 1547         @param path directory name of the project (string)
 1548         @param tags flag indicating listing of branches or tags
 1549                 (False = branches, True = tags)
 1550         """
 1551         from .HgTagBranchListDialog import HgTagBranchListDialog
 1552         self.tagbranchList = HgTagBranchListDialog(self)
 1553         self.tagbranchList.show()
 1554         if tags:
 1555             if not self.showedTags:
 1556                 self.showedTags = True
 1557                 allTagsBranchesList = self.allTagsBranchesList
 1558             else:
 1559                 self.tagsList = []
 1560                 allTagsBranchesList = None
 1561             self.tagbranchList.start(path, tags,
 1562                                      self.tagsList, allTagsBranchesList)
 1563         else:
 1564             if not self.showedBranches:
 1565                 self.showedBranches = True
 1566                 allTagsBranchesList = self.allTagsBranchesList
 1567             else:
 1568                 self.branchesList = []
 1569                 allTagsBranchesList = None
 1570             self.tagbranchList.start(path, tags,
 1571                                      self.branchesList,
 1572                                      self.allTagsBranchesList)
 1573     
 1574     def hgAnnotate(self, name):
 1575         """
 1576         Public method to show the output of the hg annotate command.
 1577         
 1578         @param name file name to show the annotations for (string)
 1579         """
 1580         if self.annotate is None:
 1581             from .HgAnnotateDialog import HgAnnotateDialog
 1582             self.annotate = HgAnnotateDialog(self)
 1583         self.annotate.show()
 1584         self.annotate.raise_()
 1585         self.annotate.start(name)
 1586     
 1587     def hgExtendedDiff(self, name):
 1588         """
 1589         Public method used to view the difference of a file/directory to the
 1590         Mercurial repository.
 1591         
 1592         If name is a directory and is the project directory, all project files
 1593         are saved first. If name is a file (or list of files), which is/are
 1594         being edited and has unsaved modification, they can be saved or the
 1595         operation may be aborted.
 1596         
 1597         This method gives the chance to enter the revisions to be compared.
 1598         
 1599         @param name file/directory name to be diffed (string)
 1600         """
 1601         if isinstance(name, list):
 1602             dname, fnames = self.splitPathList(name)
 1603             names = name[:]
 1604         else:
 1605             dname, fname = self.splitPath(name)
 1606             names = [name]
 1607         for nam in names:
 1608             if os.path.isfile(nam):
 1609                 editor = e5App().getObject("ViewManager").getOpenEditor(nam)
 1610                 if editor and not editor.checkDirty():
 1611                     return
 1612             else:
 1613                 project = e5App().getObject("Project")
 1614                 if nam == project.ppath and not project.saveAllScripts():
 1615                     return
 1616         
 1617         # find the root of the repo
 1618         repodir = dname
 1619         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 1620             repodir = os.path.dirname(repodir)
 1621             if os.path.splitdrive(repodir)[1] == os.sep:
 1622                 return
 1623         
 1624         from .HgRevisionsSelectionDialog import HgRevisionsSelectionDialog
 1625         dlg = HgRevisionsSelectionDialog(self.hgGetTagsList(repodir),
 1626                                          self.hgGetBranchesList(repodir),
 1627                                          self.hgGetBookmarksList(repodir))
 1628         if dlg.exec_() == QDialog.Accepted:
 1629             revisions = dlg.getRevisions()
 1630             if self.diff is None:
 1631                 from .HgDiffDialog import HgDiffDialog
 1632                 self.diff = HgDiffDialog(self)
 1633             self.diff.show()
 1634             self.diff.raise_()
 1635             self.diff.start(name, revisions)
 1636     
 1637     def __hgGetFileForRevision(self, name, rev=""):
 1638         """
 1639         Private method to get a file for a specific revision from the
 1640         repository.
 1641         
 1642         @param name file name to get from the repository (string)
 1643         @keyparam rev revision to retrieve (string)
 1644         @return contents of the file (string) and an error message (string)
 1645         """
 1646         args = self.initCommand("cat")
 1647         if rev:
 1648             args.append("--rev")
 1649             args.append(rev)
 1650         args.append(name)
 1651         
 1652         output, error = self.__client.runcommand(args)
 1653         
 1654         # return file contents with 'universal newlines'
 1655         return output.replace('\r\n', '\n').replace('\r', '\n'), error
 1656     
 1657     def hgSbsDiff(self, name, extended=False, revisions=None):
 1658         """
 1659         Public method used to view the difference of a file to the Mercurial
 1660         repository side-by-side.
 1661         
 1662         @param name file name to be diffed (string)
 1663         @keyparam extended flag indicating the extended variant (boolean)
 1664         @keyparam revisions tuple of two revisions (tuple of strings)
 1665         @exception ValueError raised to indicate an invalid name parameter
 1666         """
 1667         if isinstance(name, list):
 1668             raise ValueError("Wrong parameter type")
 1669         
 1670         if extended:
 1671             # find the root of the repo
 1672             repodir = self.splitPath(name)[0]
 1673             while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 1674                 repodir = os.path.dirname(repodir)
 1675                 if os.path.splitdrive(repodir)[1] == os.sep:
 1676                     return
 1677             
 1678             from .HgRevisionsSelectionDialog import HgRevisionsSelectionDialog
 1679             dlg = HgRevisionsSelectionDialog(self.hgGetTagsList(repodir),
 1680                                              self.hgGetBranchesList(repodir),
 1681                                              self.hgGetBookmarksList(repodir))
 1682             if dlg.exec_() == QDialog.Accepted:
 1683                 rev1, rev2 = dlg.getRevisions()
 1684             else:
 1685                 return
 1686         elif revisions:
 1687             rev1, rev2 = revisions[0], revisions[1]
 1688         else:
 1689             rev1, rev2 = "", ""
 1690         
 1691         output1, error = self.__hgGetFileForRevision(name, rev=rev1)
 1692         if error:
 1693             E5MessageBox.critical(
 1694                 self.__ui,
 1695                 self.tr("Mercurial Side-by-Side Difference"),
 1696                 error)
 1697             return
 1698         name1 = "{0} (rev. {1})".format(name, rev1 and rev1 or ".")
 1699         
 1700         if rev2:
 1701             output2, error = self.__hgGetFileForRevision(name, rev=rev2)
 1702             if error:
 1703                 E5MessageBox.critical(
 1704                     self.__ui,
 1705                     self.tr("Mercurial Side-by-Side Difference"),
 1706                     error)
 1707                 return
 1708             name2 = "{0} (rev. {1})".format(name, rev2)
 1709         else:
 1710             try:
 1711                 f1 = open(name, "r", encoding="utf-8")
 1712                 output2 = f1.read()
 1713                 f1.close()
 1714                 name2 = "{0} (Work)".format(name)
 1715             except IOError:
 1716                 E5MessageBox.critical(
 1717                     self.__ui,
 1718                     self.tr("Mercurial Side-by-Side Difference"),
 1719                     self.tr(
 1720                         """<p>The file <b>{0}</b> could not be read.</p>""")
 1721                     .format(name))
 1722                 return
 1723         
 1724         if self.sbsDiff is None:
 1725             from UI.CompareDialog import CompareDialog
 1726             self.sbsDiff = CompareDialog()
 1727         self.sbsDiff.show()
 1728         self.sbsDiff.raise_()
 1729         self.sbsDiff.compare(output1, output2, name1, name2)
 1730     
 1731     def vcsLogBrowser(self, name, isFile=False):
 1732         """
 1733         Public method used to browse the log of a file/directory from the
 1734         Mercurial repository.
 1735         
 1736         @param name file/directory name to show the log of (string)
 1737         @keyparam isFile flag indicating log for a file is to be shown
 1738             (boolean)
 1739         """
 1740         if self.logBrowser is None:
 1741             from .HgLogBrowserDialog import HgLogBrowserDialog
 1742             self.logBrowser = HgLogBrowserDialog(self)
 1743         self.logBrowser.show()
 1744         self.logBrowser.raise_()
 1745         self.logBrowser.start(name, isFile=isFile)
 1746     
 1747     def hgIncoming(self, name):
 1748         """
 1749         Public method used to view the log of incoming changes from the
 1750         Mercurial repository.
 1751         
 1752         @param name file/directory name to show the log of (string)
 1753         """
 1754         if self.logBrowserIncoming is None:
 1755             from .HgLogBrowserDialog import HgLogBrowserDialog
 1756             self.logBrowserIncoming = HgLogBrowserDialog(
 1757                 self, mode="incoming")
 1758         self.logBrowserIncoming.show()
 1759         self.logBrowserIncoming.raise_()
 1760         self.logBrowserIncoming.start(name)
 1761     
 1762     def hgOutgoing(self, name):
 1763         """
 1764         Public method used to view the log of outgoing changes from the
 1765         Mercurial repository.
 1766         
 1767         @param name file/directory name to show the log of (string)
 1768         """
 1769         if self.logBrowserOutgoing is None:
 1770             from .HgLogBrowserDialog import HgLogBrowserDialog
 1771             self.logBrowserOutgoing = HgLogBrowserDialog(
 1772                 self, mode="outgoing")
 1773         self.logBrowserOutgoing.show()
 1774         self.logBrowserOutgoing.raise_()
 1775         self.logBrowserOutgoing.start(name)
 1776     
 1777     def hgPull(self, name, revisions=None):
 1778         """
 1779         Public method used to pull changes from a remote Mercurial repository.
 1780         
 1781         @param name directory name of the project to be pulled to
 1782         @type str
 1783         @param revisions list of revisions to be pulled
 1784         @type list of str
 1785         @return flag indicating, that the update contained an add
 1786             or delete
 1787         @rtype bool
 1788         """
 1789         if (
 1790             self.getPlugin().getPreferences("PreferUnbundle") and
 1791             self.bundleFile and
 1792             os.path.exists(self.bundleFile) and
 1793             revisions is None
 1794         ):
 1795             command = "unbundle"
 1796             title = self.tr('Apply changegroups')
 1797         else:
 1798             command = "pull"
 1799             title = self.tr('Pulling from a remote Mercurial repository')
 1800         
 1801         args = self.initCommand(command)
 1802         args.append('-v')
 1803         if self.getPlugin().getPreferences("PullUpdate"):
 1804             args.append('--update')
 1805         if command == "unbundle":
 1806             args.append(self.bundleFile)
 1807         if revisions:
 1808             for rev in revisions:
 1809                 args.append("--rev")
 1810                 args.append(rev)
 1811         
 1812         # find the root of the repo
 1813         repodir = self.splitPath(name)[0]
 1814         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 1815             repodir = os.path.dirname(repodir)
 1816             if os.path.splitdrive(repodir)[1] == os.sep:
 1817                 return False
 1818         
 1819         dia = HgDialog(title, self)
 1820         res = dia.startProcess(args, repodir)
 1821         if res:
 1822             dia.exec_()
 1823             res = dia.hasAddOrDelete()
 1824         if (
 1825             self.bundleFile and
 1826             os.path.exists(self.bundleFile)
 1827         ):
 1828             os.remove(self.bundleFile)
 1829             self.bundleFile = None
 1830         self.checkVCSStatus()
 1831         return res
 1832     
 1833     def hgPush(self, name, force=False, newBranch=False, rev=None):
 1834         """
 1835         Public method used to push changes to a remote Mercurial repository.
 1836         
 1837         @param name directory name of the project to be pushed from (string)
 1838         @keyparam force flag indicating a forced push (boolean)
 1839         @keyparam newBranch flag indicating to push a new branch (boolean)
 1840         @keyparam rev revision to be pushed (including all ancestors) (string)
 1841         """
 1842         args = self.initCommand("push")
 1843         args.append('-v')
 1844         if force:
 1845             args.append('-f')
 1846         if newBranch:
 1847             args.append('--new-branch')
 1848         if rev:
 1849             args.append('--rev')
 1850             args.append(rev)
 1851         
 1852         # find the root of the repo
 1853         repodir = self.splitPath(name)[0]
 1854         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 1855             repodir = os.path.dirname(repodir)
 1856             if os.path.splitdrive(repodir)[1] == os.sep:
 1857                 return
 1858         
 1859         dia = HgDialog(
 1860             self.tr('Pushing to a remote Mercurial repository'), self)
 1861         res = dia.startProcess(args, repodir)
 1862         if res:
 1863             dia.exec_()
 1864         self.checkVCSStatus()
 1865     
 1866     def hgInfo(self, ppath, mode="heads"):
 1867         """
 1868         Public method to show information about the heads of the repository.
 1869         
 1870         @param ppath local path to get the repository infos (string)
 1871         @keyparam mode mode of the operation (string, one of heads, parents,
 1872             tip)
 1873         """
 1874         if mode not in ("heads", "parents", "tip"):
 1875             mode = "heads"
 1876         
 1877         info = []
 1878         
 1879         args = self.initCommand(mode)
 1880         args.append('--template')
 1881         args.append('{rev}:{node|short}@@@{tags}@@@{author|xmlescape}@@@'
 1882                     '{date|isodate}@@@{branches}@@@{parents}@@@{bookmarks}\n')
 1883         
 1884         output, error = self.__client.runcommand(args)
 1885         
 1886         if output:
 1887             index = 0
 1888             for line in output.splitlines():
 1889                 index += 1
 1890                 (changeset, tags, author, date, branches, parents,
 1891                  bookmarks) = line.split("@@@")
 1892                 cdate, ctime = date.split()[:2]
 1893                 info.append("""<p><table>""")
 1894                 if mode == "heads":
 1895                     info.append(QCoreApplication.translate(
 1896                         "mercurial",
 1897                         """<tr><td><b>Head #{0}</b></td><td></td></tr>\n""")
 1898                         .format(index))
 1899                 elif mode == "parents":
 1900                     info.append(QCoreApplication.translate(
 1901                         "mercurial",
 1902                         """<tr><td><b>Parent #{0}</b></td><td></td></tr>\n""")
 1903                         .format(index))
 1904                 elif mode == "tip":
 1905                     info.append(QCoreApplication.translate(
 1906                         "mercurial",
 1907                         """<tr><td><b>Tip</b></td><td></td></tr>\n"""))
 1908                 info.append(QCoreApplication.translate(
 1909                     "mercurial",
 1910                     """<tr><td><b>Changeset</b></td><td>{0}</td></tr>""")
 1911                     .format(changeset))
 1912                 if tags:
 1913                     info.append(QCoreApplication.translate(
 1914                         "mercurial",
 1915                         """<tr><td><b>Tags</b></td><td>{0}</td></tr>""")
 1916                         .format('<br/>'.join(tags.split())))
 1917                 if bookmarks:
 1918                     info.append(QCoreApplication.translate(
 1919                         "mercurial",
 1920                         """<tr><td><b>Bookmarks</b></td><td>{0}</td></tr>""")
 1921                         .format('<br/>'.join(bookmarks.split())))
 1922                 if branches:
 1923                     info.append(QCoreApplication.translate(
 1924                         "mercurial",
 1925                         """<tr><td><b>Branches</b></td><td>{0}</td></tr>""")
 1926                         .format('<br/>'.join(branches.split())))
 1927                 if parents:
 1928                     info.append(QCoreApplication.translate(
 1929                         "mercurial",
 1930                         """<tr><td><b>Parents</b></td><td>{0}</td></tr>""")
 1931                         .format('<br/>'.join(parents.split())))
 1932                 info.append(QCoreApplication.translate(
 1933                     "mercurial",
 1934                     """<tr><td><b>Last author</b></td><td>{0}</td></tr>\n"""
 1935                     """<tr><td><b>Committed date</b></td><td>{1}</td></tr>\n"""
 1936                     """<tr><td><b>Committed time</b></td><td>{2}</td></tr>\n"""
 1937                     """</table></p>""")
 1938                     .format(author, cdate, ctime))
 1939             
 1940             dlg = VcsRepositoryInfoDialog(None, "\n".join(info))
 1941             dlg.exec_()
 1942     
 1943     def hgConflicts(self, name):
 1944         """
 1945         Public method used to show a list of files containing conflicts.
 1946         
 1947         @param name file/directory name to be resolved (string)
 1948         """
 1949         if self.conflictsDlg is None:
 1950             from .HgConflictsListDialog import HgConflictsListDialog
 1951             self.conflictsDlg = HgConflictsListDialog(self)
 1952         self.conflictsDlg.show()
 1953         self.conflictsDlg.raise_()
 1954         self.conflictsDlg.start(name)
 1955     
 1956     def hgResolved(self, name, unresolve=False):
 1957         """
 1958         Public method used to resolve conflicts of a file/directory.
 1959         
 1960         @param name file/directory name to be resolved (string)
 1961         @param unresolve flag indicating to mark the file/directory as
 1962             unresolved (boolean)
 1963         """
 1964         args = self.initCommand("resolve")
 1965         if unresolve:
 1966             args.append("--unmark")
 1967         else:
 1968             args.append("--mark")
 1969         
 1970         if isinstance(name, list):
 1971             dname, fnames = self.splitPathList(name)
 1972             self.addArguments(args, name)
 1973         else:
 1974             dname, fname = self.splitPath(name)
 1975             args.append(name)
 1976         
 1977         # find the root of the repo
 1978         repodir = dname
 1979         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 1980             repodir = os.path.dirname(repodir)
 1981             if os.path.splitdrive(repodir)[1] == os.sep:
 1982                 return
 1983         
 1984         if unresolve:
 1985             title = self.tr("Marking as 'unresolved'")
 1986         else:
 1987             title = self.tr("Marking as 'resolved'")
 1988         dia = HgDialog(title, self)
 1989         res = dia.startProcess(args, repodir)
 1990         if res:
 1991             dia.exec_()
 1992         self.checkVCSStatus()
 1993     
 1994     def hgAbortMerge(self, name):
 1995         """
 1996         Public method to abort an uncommitted merge.
 1997         
 1998         @param name file/directory name (string)
 1999         @return flag indicating, that the abortion contained an add
 2000             or delete (boolean)
 2001         """
 2002         dname, fname = self.splitPath(name)
 2003         
 2004         # find the root of the repo
 2005         repodir = dname
 2006         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2007             repodir = os.path.dirname(repodir)
 2008             if os.path.splitdrive(repodir)[1] == os.sep:
 2009                 return False
 2010         
 2011         if self.version >= (4, 5, 0):
 2012             args = self.initCommand("merge")
 2013             args.append("--abort")
 2014         else:
 2015             args = self.initCommand("update")
 2016             args.append("--clean")
 2017         
 2018         dia = HgDialog(
 2019             self.tr('Aborting uncommitted merge'),
 2020             self)
 2021         res = dia.startProcess(args, repodir, False)
 2022         if res:
 2023             dia.exec_()
 2024             res = dia.hasAddOrDelete()
 2025         self.checkVCSStatus()
 2026         return res
 2027     
 2028     def hgBranch(self, name):
 2029         """
 2030         Public method used to create a branch in the Mercurial repository.
 2031         
 2032         @param name file/directory name to be branched (string)
 2033         """
 2034         dname, fname = self.splitPath(name)
 2035         
 2036         # find the root of the repo
 2037         repodir = dname
 2038         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2039             repodir = os.path.dirname(repodir)
 2040             if os.path.splitdrive(repodir)[1] == os.sep:
 2041                 return
 2042         
 2043         from .HgBranchInputDialog import HgBranchInputDialog
 2044         dlg = HgBranchInputDialog(self.hgGetBranchesList(repodir))
 2045         if dlg.exec_() == QDialog.Accepted:
 2046             name, commit = dlg.getData()
 2047             name = name.strip().replace(" ", "_")
 2048             args = self.initCommand("branch")
 2049             args.append(name)
 2050             
 2051             dia = HgDialog(
 2052                 self.tr('Creating branch in the Mercurial repository'),
 2053                 self)
 2054             res = dia.startProcess(args, repodir)
 2055             if res:
 2056                 dia.exec_()
 2057                 if commit:
 2058                     self.vcsCommit(
 2059                         repodir,
 2060                         self.tr("Created new branch <{0}>.").format(
 2061                             name))
 2062     
 2063     def hgShowBranch(self, name):
 2064         """
 2065         Public method used to show the current branch of the working directory.
 2066         
 2067         @param name file/directory name (string)
 2068         """
 2069         dname, fname = self.splitPath(name)
 2070         
 2071         # find the root of the repo
 2072         repodir = dname
 2073         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2074             repodir = os.path.dirname(repodir)
 2075             if os.path.splitdrive(repodir)[1] == os.sep:
 2076                 return
 2077         
 2078         args = self.initCommand("branch")
 2079         
 2080         dia = HgDialog(self.tr('Showing current branch'), self)
 2081         res = dia.startProcess(args, repodir, False)
 2082         if res:
 2083             dia.exec_()
 2084     
 2085     def hgGetCurrentBranch(self, repodir):
 2086         """
 2087         Public method to get the current branch of the working directory.
 2088         
 2089         @param repodir directory name of the repository
 2090         @type str
 2091         @return name of the current branch
 2092         @rtype str
 2093         """
 2094         args = self.initCommand("branch")
 2095         
 2096         output, error = self.__client.runcommand(args)
 2097         
 2098         return output.strip()
 2099     
 2100     def hgEditUserConfig(self):
 2101         """
 2102         Public method used to edit the user configuration file.
 2103         """
 2104         from .HgUserConfigDialog import HgUserConfigDialog
 2105         dlg = HgUserConfigDialog(version=self.version)
 2106         dlg.exec_()
 2107     
 2108     def hgEditConfig(self, name, withLargefiles=True, largefilesData=None):
 2109         """
 2110         Public method used to edit the repository configuration file.
 2111         
 2112         @param name file/directory name (string)
 2113         @param withLargefiles flag indicating to configure the largefiles
 2114             section (boolean)
 2115         @param largefilesData dictionary with data for the largefiles
 2116             section of the data dialog (dict)
 2117         """
 2118         dname, fname = self.splitPath(name)
 2119         
 2120         # find the root of the repo
 2121         repodir = dname
 2122         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2123             repodir = os.path.dirname(repodir)
 2124             if os.path.splitdrive(repodir)[1] == os.sep:
 2125                 return
 2126         
 2127         cfgFile = os.path.join(repodir, self.adminDir, "hgrc")
 2128         if not os.path.exists(cfgFile):
 2129             # open dialog to enter the initial data
 2130             withLargefiles = (self.isExtensionActive("largefiles") and
 2131                               withLargefiles)
 2132             from .HgRepoConfigDataDialog import HgRepoConfigDataDialog
 2133             dlg = HgRepoConfigDataDialog(withLargefiles=withLargefiles,
 2134                                          largefilesData=largefilesData)
 2135             if dlg.exec_() == QDialog.Accepted:
 2136                 createContents = True
 2137                 defaultUrl, defaultPushUrl = dlg.getData()
 2138                 if withLargefiles:
 2139                     lfMinSize, lfPattern = dlg.getLargefilesData()
 2140             else:
 2141                 createContents = False
 2142             try:
 2143                 cfg = open(cfgFile, "w")
 2144                 if createContents:
 2145                     # write the data entered
 2146                     cfg.write("[paths]\n")
 2147                     if defaultUrl:
 2148                         cfg.write("default = {0}\n".format(defaultUrl))
 2149                     if defaultPushUrl:
 2150                         cfg.write("default-push = {0}\n".format(
 2151                             defaultPushUrl))
 2152                     if (
 2153                         withLargefiles and
 2154                         (lfMinSize, lfPattern) != (None, None)
 2155                     ):
 2156                         cfg.write("\n[largefiles]\n")
 2157                         if lfMinSize is not None:
 2158                             cfg.write("minsize = {0}\n".format(lfMinSize))
 2159                         if lfPattern is not None:
 2160                             cfg.write("patterns =\n")
 2161                             cfg.write("  {0}\n".format(
 2162                                 "\n  ".join(lfPattern)))
 2163                 cfg.close()
 2164                 self.__monitorRepoIniFile(repodir)
 2165                 self.__iniFileChanged(cfgFile)
 2166             except IOError:
 2167                 pass
 2168         self.repoEditor = MiniEditor(cfgFile, "Properties")
 2169         self.repoEditor.show()
 2170     
 2171     def hgVerify(self, name):
 2172         """
 2173         Public method to verify the integrity of the repository.
 2174         
 2175         @param name file/directory name (string)
 2176         """
 2177         dname, fname = self.splitPath(name)
 2178         
 2179         # find the root of the repo
 2180         repodir = dname
 2181         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2182             repodir = os.path.dirname(repodir)
 2183             if os.path.splitdrive(repodir)[1] == os.sep:
 2184                 return
 2185         
 2186         args = self.initCommand("verify")
 2187         
 2188         dia = HgDialog(
 2189             self.tr('Verifying the integrity of the Mercurial repository'),
 2190             self)
 2191         res = dia.startProcess(args, repodir)
 2192         if res:
 2193             dia.exec_()
 2194     
 2195     def hgShowConfig(self, name):
 2196         """
 2197         Public method to show the combined configuration.
 2198         
 2199         @param name file/directory name (string)
 2200         """
 2201         dname, fname = self.splitPath(name)
 2202         
 2203         # find the root of the repo
 2204         repodir = dname
 2205         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2206             repodir = os.path.dirname(repodir)
 2207             if os.path.splitdrive(repodir)[1] == os.sep:
 2208                 return
 2209         
 2210         args = self.initCommand("showconfig")
 2211         args.append("--untrusted")
 2212         
 2213         dia = HgDialog(
 2214             self.tr('Showing the combined configuration settings'),
 2215             self)
 2216         res = dia.startProcess(args, repodir, False)
 2217         if res:
 2218             dia.exec_()
 2219     
 2220     def hgShowPaths(self, name):
 2221         """
 2222         Public method to show the path aliases for remote repositories.
 2223         
 2224         @param name file/directory name (string)
 2225         """
 2226         dname, fname = self.splitPath(name)
 2227         
 2228         # find the root of the repo
 2229         repodir = dname
 2230         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2231             repodir = os.path.dirname(repodir)
 2232             if os.path.splitdrive(repodir)[1] == os.sep:
 2233                 return
 2234         
 2235         args = self.initCommand("paths")
 2236         
 2237         dia = HgDialog(
 2238             self.tr('Showing aliases for remote repositories'),
 2239             self)
 2240         res = dia.startProcess(args, repodir, False)
 2241         if res:
 2242             dia.exec_()
 2243     
 2244     def hgRecover(self, name):
 2245         """
 2246         Public method to recover an interrupted transaction.
 2247         
 2248         @param name file/directory name (string)
 2249         """
 2250         dname, fname = self.splitPath(name)
 2251         
 2252         # find the root of the repo
 2253         repodir = dname
 2254         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2255             repodir = os.path.dirname(repodir)
 2256             if os.path.splitdrive(repodir)[1] == os.sep:
 2257                 return
 2258         
 2259         args = self.initCommand("recover")
 2260         
 2261         dia = HgDialog(
 2262             self.tr('Recovering from interrupted transaction'),
 2263             self)
 2264         res = dia.startProcess(args, repodir, False)
 2265         if res:
 2266             dia.exec_()
 2267     
 2268     def hgIdentify(self, name):
 2269         """
 2270         Public method to identify the current working directory.
 2271         
 2272         @param name file/directory name (string)
 2273         """
 2274         dname, fname = self.splitPath(name)
 2275         
 2276         # find the root of the repo
 2277         repodir = dname
 2278         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2279             repodir = os.path.dirname(repodir)
 2280             if os.path.splitdrive(repodir)[1] == os.sep:
 2281                 return
 2282         
 2283         args = self.initCommand("identify")
 2284         
 2285         dia = HgDialog(self.tr('Identifying project directory'), self)
 2286         res = dia.startProcess(args, repodir, False)
 2287         if res:
 2288             dia.exec_()
 2289     
 2290     def hgCreateIgnoreFile(self, name, autoAdd=False):
 2291         """
 2292         Public method to create the ignore file.
 2293         
 2294         @param name directory name to create the ignore file in (string)
 2295         @param autoAdd flag indicating to add it automatically (boolean)
 2296         @return flag indicating success
 2297         """
 2298         status = False
 2299         ignorePatterns = [
 2300             "glob:.eric6project",
 2301             "glob:.ropeproject",
 2302             "glob:.directory",
 2303             "glob:**.pyc",
 2304             "glob:**.pyo",
 2305             "glob:**.orig",
 2306             "glob:**.bak",
 2307             "glob:**.rej",
 2308             "glob:**~",
 2309             "glob:cur",
 2310             "glob:tmp",
 2311             "glob:__pycache__",
 2312             "glob:**.DS_Store",
 2313         ]
 2314         
 2315         ignoreName = os.path.join(name, Hg.IgnoreFileName)
 2316         if os.path.exists(ignoreName):
 2317             res = E5MessageBox.yesNo(
 2318                 self.__ui,
 2319                 self.tr("Create .hgignore file"),
 2320                 self.tr("""<p>The file <b>{0}</b> exists already."""
 2321                         """ Overwrite it?</p>""").format(ignoreName),
 2322                 icon=E5MessageBox.Warning)
 2323         else:
 2324             res = True
 2325         if res:
 2326             try:
 2327                 # create a .hgignore file
 2328                 ignore = open(ignoreName, "w")
 2329                 ignore.write("\n".join(ignorePatterns))
 2330                 ignore.write("\n")
 2331                 ignore.close()
 2332                 status = True
 2333             except IOError:
 2334                 status = False
 2335             
 2336             if status and autoAdd:
 2337                 self.vcsAdd(ignoreName, noDialog=True)
 2338                 project = e5App().getObject("Project")
 2339                 project.appendFile(ignoreName)
 2340         
 2341         return status
 2342     
 2343     def hgBundle(self, name, bundleData=None):
 2344         """
 2345         Public method to create a changegroup file.
 2346         
 2347         @param name file/directory name
 2348         @type str
 2349         @param bundleData dictionary containing the bundle creation information
 2350         @type dict
 2351         """
 2352         dname, fname = self.splitPath(name)
 2353         
 2354         # find the root of the repo
 2355         repodir = dname
 2356         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2357             repodir = os.path.dirname(repodir)
 2358             if os.path.splitdrive(repodir)[1] == os.sep:
 2359                 return
 2360         
 2361         if bundleData is None:
 2362             from .HgBundleDialog import HgBundleDialog
 2363             dlg = HgBundleDialog(self.hgGetTagsList(repodir),
 2364                                  self.hgGetBranchesList(repodir),
 2365                                  self.hgGetBookmarksList(repodir),
 2366                                  version=self.version)
 2367             if dlg.exec_() != QDialog.Accepted:
 2368                 return
 2369             
 2370             revs, baseRevs, compression, bundleAll = dlg.getParameters()
 2371         else:
 2372             revs = bundleData["revs"]
 2373             if bundleData["base"]:
 2374                 baseRevs = [bundleData["base"]]
 2375             else:
 2376                 baseRevs = []
 2377             compression = ""
 2378             bundleAll = bundleData["all"]
 2379         
 2380         fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
 2381             None,
 2382             self.tr("Create changegroup"),
 2383             self.__lastChangeGroupPath or repodir,
 2384             self.tr("Mercurial Changegroup Files (*.hg)"),
 2385             None,
 2386             E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
 2387         
 2388         if not fname:
 2389             return  # user aborted
 2390         
 2391         ext = QFileInfo(fname).suffix()
 2392         if not ext:
 2393             ex = selectedFilter.split("(*")[1].split(")")[0]
 2394             if ex:
 2395                 fname += ex
 2396         if QFileInfo(fname).exists():
 2397             res = E5MessageBox.yesNo(
 2398                 self.__ui,
 2399                 self.tr("Create changegroup"),
 2400                 self.tr("<p>The Mercurial changegroup file <b>{0}</b> "
 2401                         "already exists. Overwrite it?</p>")
 2402                     .format(fname),
 2403                 icon=E5MessageBox.Warning)
 2404             if not res:
 2405                 return
 2406         fname = Utilities.toNativeSeparators(fname)
 2407         self.__lastChangeGroupPath = os.path.dirname(fname)
 2408         
 2409         args = self.initCommand("bundle")
 2410         if bundleAll:
 2411             args.append("--all")
 2412         for rev in revs:
 2413             args.append("--rev")
 2414             args.append(rev)
 2415         for baseRev in baseRevs:
 2416             args.append("--base")
 2417             args.append(baseRev)
 2418         if compression:
 2419             args.append("--type")
 2420             args.append(compression)
 2421         args.append(fname)
 2422         
 2423         dia = HgDialog(self.tr('Create changegroup'), self)
 2424         res = dia.startProcess(args, repodir)
 2425         if res:
 2426             dia.exec_()
 2427     
 2428     def hgPreviewBundle(self, name):
 2429         """
 2430         Public method used to view the log of incoming changes from a
 2431         changegroup file.
 2432         
 2433         @param name directory name on which to base the changegroup (string)
 2434         """
 2435         dname, fname = self.splitPath(name)
 2436         
 2437         # find the root of the repo
 2438         repodir = dname
 2439         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2440             repodir = os.path.dirname(repodir)
 2441             if os.path.splitdrive(repodir)[1] == os.sep:
 2442                 return
 2443         
 2444         file = E5FileDialog.getOpenFileName(
 2445             None,
 2446             self.tr("Preview changegroup"),
 2447             self.__lastChangeGroupPath or repodir,
 2448             self.tr("Mercurial Changegroup Files (*.hg);;All Files (*)"))
 2449         if file:
 2450             self.__lastChangeGroupPath = os.path.dirname(file)
 2451             
 2452             if self.logBrowserIncoming is None:
 2453                 from .HgLogBrowserDialog import HgLogBrowserDialog
 2454                 self.logBrowserIncoming = HgLogBrowserDialog(
 2455                     self, mode="incoming")
 2456             self.logBrowserIncoming.show()
 2457             self.logBrowserIncoming.raise_()
 2458             self.logBrowserIncoming.start(name, bundle=file)
 2459     
 2460     def hgUnbundle(self, name, files=None):
 2461         """
 2462         Public method to apply changegroup files.
 2463         
 2464         @param name directory name
 2465         @type str
 2466         @param files list of bundle files to be applied
 2467         @type list of str
 2468         @return flag indicating, that the update contained an add
 2469             or delete
 2470         @rtype bool
 2471         """
 2472         dname, fname = self.splitPath(name)
 2473         
 2474         # find the root of the repo
 2475         repodir = dname
 2476         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2477             repodir = os.path.dirname(repodir)
 2478             if os.path.splitdrive(repodir)[1] == os.sep:
 2479                 return False
 2480         
 2481         res = False
 2482         if not files:
 2483             files = E5FileDialog.getOpenFileNames(
 2484                 None,
 2485                 self.tr("Apply changegroups"),
 2486                 self.__lastChangeGroupPath or repodir,
 2487                 self.tr("Mercurial Changegroup Files (*.hg);;All Files (*)"))
 2488         
 2489         if files:
 2490             self.__lastChangeGroupPath = os.path.dirname(files[0])
 2491             
 2492             update = E5MessageBox.yesNo(
 2493                 self.__ui,
 2494                 self.tr("Apply changegroups"),
 2495                 self.tr("""Shall the working directory be updated?"""),
 2496                 yesDefault=True)
 2497             
 2498             args = self.initCommand("unbundle")
 2499             if update:
 2500                 args.append("--update")
 2501                 args.append("--verbose")
 2502             args.extend(files)
 2503             
 2504             dia = HgDialog(self.tr('Apply changegroups'), self)
 2505             res = dia.startProcess(args, repodir)
 2506             if res:
 2507                 dia.exec_()
 2508                 res = dia.hasAddOrDelete()
 2509             self.checkVCSStatus()
 2510         
 2511         return res
 2512     
 2513     def hgBisect(self, name, subcommand):
 2514         """
 2515         Public method to perform bisect commands.
 2516         
 2517         @param name file/directory name (string)
 2518         @param subcommand name of the subcommand (string, one of 'good', 'bad',
 2519             'skip' or 'reset')
 2520         @exception ValueError raised to indicate an invalid bisect subcommand
 2521         """
 2522         if subcommand not in ("good", "bad", "skip", "reset"):
 2523             raise ValueError(
 2524                 self.tr("Bisect subcommand ({0}) invalid.")
 2525                     .format(subcommand))
 2526         
 2527         dname, fname = self.splitPath(name)
 2528         
 2529         # find the root of the repo
 2530         repodir = dname
 2531         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2532             repodir = os.path.dirname(repodir)
 2533             if os.path.splitdrive(repodir)[1] == os.sep:
 2534                 return
 2535         
 2536         rev = ""
 2537         if subcommand in ("good", "bad", "skip"):
 2538             from .HgRevisionSelectionDialog import HgRevisionSelectionDialog
 2539             dlg = HgRevisionSelectionDialog(self.hgGetTagsList(repodir),
 2540                                             self.hgGetBranchesList(repodir),
 2541                                             self.hgGetBookmarksList(repodir))
 2542             if dlg.exec_() == QDialog.Accepted:
 2543                 rev = dlg.getRevision()
 2544             else:
 2545                 return
 2546         
 2547         args = self.initCommand("bisect")
 2548         args.append("--{0}".format(subcommand))
 2549         if rev:
 2550             args.append(rev)
 2551         
 2552         dia = HgDialog(
 2553             self.tr('Mercurial Bisect ({0})').format(subcommand), self)
 2554         res = dia.startProcess(args, repodir)
 2555         if res:
 2556             dia.exec_()
 2557     
 2558     def hgForget(self, name):
 2559         """
 2560         Public method used to remove a file from the Mercurial repository.
 2561         
 2562         This will not remove the file from the project directory.
 2563         
 2564         @param name file/directory name to be removed (string or list of
 2565             strings))
 2566         """
 2567         args = self.initCommand("forget")
 2568         args.append('-v')
 2569         
 2570         if isinstance(name, list):
 2571             dname, fnames = self.splitPathList(name)
 2572             self.addArguments(args, name)
 2573         else:
 2574             dname, fname = self.splitPath(name)
 2575             args.append(name)
 2576         
 2577         # find the root of the repo
 2578         repodir = dname
 2579         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2580             repodir = os.path.dirname(repodir)
 2581             if os.path.splitdrive(repodir)[1] == os.sep:
 2582                 return
 2583         
 2584         dia = HgDialog(
 2585             self.tr('Removing files from the Mercurial repository only'),
 2586             self)
 2587         res = dia.startProcess(args, repodir)
 2588         if res:
 2589             dia.exec_()
 2590             if isinstance(name, list):
 2591                 self.__forgotNames.extend(name)
 2592             else:
 2593                 self.__forgotNames.append(name)
 2594     
 2595     def hgBackout(self, name):
 2596         """
 2597         Public method used to backout an earlier changeset from the Mercurial
 2598         repository.
 2599         
 2600         @param name directory name (string or list of strings)
 2601         """
 2602         dname, fname = self.splitPath(name)
 2603         
 2604         # find the root of the repo
 2605         repodir = dname
 2606         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2607             repodir = os.path.dirname(repodir)
 2608             if os.path.splitdrive(repodir)[1] == os.sep:
 2609                 return
 2610         
 2611         from .HgBackoutDialog import HgBackoutDialog
 2612         dlg = HgBackoutDialog(self.hgGetTagsList(repodir),
 2613                               self.hgGetBranchesList(repodir),
 2614                               self.hgGetBookmarksList(repodir))
 2615         if dlg.exec_() == QDialog.Accepted:
 2616             rev, merge, date, user, message = dlg.getParameters()
 2617             if not rev:
 2618                 E5MessageBox.warning(
 2619                     self.__ui,
 2620                     self.tr("Backing out changeset"),
 2621                     self.tr("""No revision given. Aborting..."""))
 2622                 return
 2623             
 2624             args = self.initCommand("backout")
 2625             args.append('-v')
 2626             if merge:
 2627                 args.append('--merge')
 2628             if date:
 2629                 args.append('--date')
 2630                 args.append(date)
 2631             if user:
 2632                 args.append('--user')
 2633                 args.append(user)
 2634             args.append('--message')
 2635             args.append(message)
 2636             args.append(rev)
 2637             
 2638             dia = HgDialog(self.tr('Backing out changeset'), self)
 2639             res = dia.startProcess(args, repodir)
 2640             if res:
 2641                 dia.exec_()
 2642     
 2643     def hgRollback(self, name):
 2644         """
 2645         Public method used to rollback the last transaction.
 2646         
 2647         @param name directory name (string or list of strings)
 2648         """
 2649         dname, fname = self.splitPath(name)
 2650         
 2651         # find the root of the repo
 2652         repodir = dname
 2653         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2654             repodir = os.path.dirname(repodir)
 2655             if os.path.splitdrive(repodir)[1] == os.sep:
 2656                 return
 2657         
 2658         res = E5MessageBox.yesNo(
 2659             None,
 2660             self.tr("Rollback last transaction"),
 2661             self.tr("""Are you sure you want to rollback the last"""
 2662                     """ transaction?"""),
 2663             icon=E5MessageBox.Warning)
 2664         if res:
 2665             dia = HgDialog(self.tr('Rollback last transaction'), self)
 2666             res = dia.startProcess(["rollback"], repodir)
 2667             if res:
 2668                 dia.exec_()
 2669 
 2670     def hgServe(self, name):
 2671         """
 2672         Public method used to serve the project.
 2673         
 2674         @param name directory name (string)
 2675         """
 2676         dname, fname = self.splitPath(name)
 2677         
 2678         # find the root of the repo
 2679         repodir = dname
 2680         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2681             repodir = os.path.dirname(repodir)
 2682             if os.path.splitdrive(repodir)[1] == os.sep:
 2683                 return
 2684         
 2685         from .HgServeDialog import HgServeDialog
 2686         self.serveDlg = HgServeDialog(self, repodir)
 2687         self.serveDlg.show()
 2688     
 2689     def hgImport(self, name):
 2690         """
 2691         Public method to import a patch file.
 2692         
 2693         @param name directory name of the project to import into (string)
 2694         @return flag indicating, that the import contained an add, a delete
 2695             or a change to the project file (boolean)
 2696         """
 2697         dname, fname = self.splitPath(name)
 2698         
 2699         # find the root of the repo
 2700         repodir = dname
 2701         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2702             repodir = os.path.dirname(repodir)
 2703             if os.path.splitdrive(repodir)[1] == os.sep:
 2704                 return False
 2705         
 2706         from .HgImportDialog import HgImportDialog
 2707         dlg = HgImportDialog(self)
 2708         if dlg.exec_() == QDialog.Accepted:
 2709             (patchFile, noCommit, message, date, user, withSecret, stripCount,
 2710              force) = dlg.getParameters()
 2711             
 2712             args = self.initCommand("import")
 2713             args.append("--verbose")
 2714             if noCommit:
 2715                 args.append("--no-commit")
 2716             else:
 2717                 if message:
 2718                     args.append('--message')
 2719                     args.append(message)
 2720                 if date:
 2721                     args.append('--date')
 2722                     args.append(date)
 2723                 if user:
 2724                     args.append('--user')
 2725                     args.append(user)
 2726             if stripCount != 1:
 2727                 args.append("--strip")
 2728                 args.append(str(stripCount))
 2729             if force:
 2730                 args.append("--force")
 2731             if withSecret:
 2732                 args.append("--secret")
 2733             args.append(patchFile)
 2734             
 2735             dia = HgDialog(self.tr("Import Patch"), self)
 2736             res = dia.startProcess(args, repodir)
 2737             if res:
 2738                 dia.exec_()
 2739                 res = dia.hasAddOrDelete()
 2740             self.checkVCSStatus()
 2741         else:
 2742             res = False
 2743         
 2744         return res
 2745     
 2746     def hgExport(self, name):
 2747         """
 2748         Public method to export patches to files.
 2749         
 2750         @param name directory name of the project to export from (string)
 2751         """
 2752         dname, fname = self.splitPath(name)
 2753         
 2754         # find the root of the repo
 2755         repodir = dname
 2756         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2757             repodir = os.path.dirname(repodir)
 2758             if os.path.splitdrive(repodir)[1] == os.sep:
 2759                 return
 2760         
 2761         from .HgExportDialog import HgExportDialog
 2762         dlg = HgExportDialog(self.hgGetBookmarksList(repodir),
 2763                              self.version >= (4, 7, 0))
 2764         if dlg.exec_() == QDialog.Accepted:
 2765             (filePattern, revisions, bookmark, switchParent, allText,
 2766              noDates, git) = dlg.getParameters()
 2767             
 2768             args = self.initCommand("export")
 2769             args.append("--output")
 2770             args.append(filePattern)
 2771             args.append("--verbose")
 2772             if switchParent:
 2773                 args.append("--switch-parent")
 2774             if allText:
 2775                 args.append("--text")
 2776             if noDates:
 2777                 args.append("--nodates")
 2778             if git:
 2779                 args.append("--git")
 2780             if bookmark:
 2781                 args.append("--bookmark")
 2782                 args.append(bookmark)
 2783             else:
 2784                 for rev in revisions:
 2785                     args.append(rev)
 2786             
 2787             dia = HgDialog(self.tr("Export Patches"), self)
 2788             res = dia.startProcess(args, repodir)
 2789             if res:
 2790                 dia.exec_()
 2791     
 2792     def hgPhase(self, name, data=None):
 2793         """
 2794         Public method to change the phase of revisions.
 2795         
 2796         @param name directory name of the project to export from (string)
 2797         @param data tuple giving phase data (list of revisions, phase, flag
 2798             indicating a forced operation) (list of strings, string, boolean)
 2799         @return flag indicating success (boolean)
 2800         @exception ValueError raised to indicate an invalid phase
 2801         """
 2802         dname, fname = self.splitPath(name)
 2803         
 2804         # find the root of the repo
 2805         repodir = dname
 2806         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2807             repodir = os.path.dirname(repodir)
 2808             if os.path.splitdrive(repodir)[1] == os.sep:
 2809                 return False
 2810         
 2811         if data is None:
 2812             from .HgPhaseDialog import HgPhaseDialog
 2813             dlg = HgPhaseDialog()
 2814             if dlg.exec_() == QDialog.Accepted:
 2815                 data = dlg.getData()
 2816         
 2817         if data:
 2818             revs, phase, force = data
 2819             
 2820             args = self.initCommand("phase")
 2821             if phase == "p":
 2822                 args.append("--public")
 2823             elif phase == "d":
 2824                 args.append("--draft")
 2825             elif phase == "s":
 2826                 args.append("--secret")
 2827             else:
 2828                 raise ValueError("Invalid phase given.")
 2829             if force:
 2830                 args.append("--force")
 2831             for rev in revs:
 2832                 args.append(rev)
 2833             
 2834             dia = HgDialog(self.tr("Change Phase"), self)
 2835             res = dia.startProcess(args, repodir)
 2836             if res:
 2837                 dia.exec_()
 2838                 res = dia.normalExitWithoutErrors()
 2839         else:
 2840             res = False
 2841         
 2842         return res
 2843     
 2844     def hgGraft(self, path, revs=None):
 2845         """
 2846         Public method to copy changesets from another branch.
 2847         
 2848         @param path directory name of the project (string)
 2849         @param revs list of revisions to show in the revisions pane (list of
 2850             strings)
 2851         @return flag indicating that the project should be reread (boolean)
 2852         """
 2853         # find the root of the repo
 2854         repodir = self.splitPath(path)[0]
 2855         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2856             repodir = os.path.dirname(repodir)
 2857             if os.path.splitdrive(repodir)[1] == os.sep:
 2858                 return False
 2859         
 2860         from .HgGraftDialog import HgGraftDialog
 2861         res = False
 2862         dlg = HgGraftDialog(self, revs)
 2863         if dlg.exec_() == QDialog.Accepted:
 2864             (revs,
 2865              (userData, currentUser, userName),
 2866              (dateData, currentDate, dateStr),
 2867              log, dryrun, noCommit) = dlg.getData()
 2868             
 2869             args = self.initCommand("graft")
 2870             args.append("--verbose")
 2871             if userData:
 2872                 if currentUser:
 2873                     args.append("--currentuser")
 2874                 else:
 2875                     args.append("--user")
 2876                     args.append(userName)
 2877             if dateData:
 2878                 if currentDate:
 2879                     args.append("--currentdate")
 2880                 else:
 2881                     args.append("--date")
 2882                     args.append(dateStr)
 2883             if log:
 2884                 args.append("--log")
 2885             if dryrun:
 2886                 args.append("--dry-run")
 2887             if noCommit:
 2888                 args.append("--no-commit")
 2889             args.extend(revs)
 2890             
 2891             dia = HgDialog(self.tr('Copy Changesets'), self)
 2892             res = dia.startProcess(args, repodir)
 2893             if res:
 2894                 dia.exec_()
 2895                 res = dia.hasAddOrDelete()
 2896                 self.checkVCSStatus()
 2897         return res
 2898     
 2899     def __hgGraftSubCommand(self, path, subcommand, title):
 2900         """
 2901         Private method to perform a Mercurial graft subcommand.
 2902         
 2903         @param path directory name of the project
 2904         @type str
 2905         @param subcommand subcommand flag
 2906         @type str
 2907         @param title tirle of the dialog
 2908         @type str
 2909         @return flag indicating that the project should be reread
 2910         @rtype bool
 2911         """
 2912         # find the root of the repo
 2913         repodir = self.splitPath(path)[0]
 2914         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2915             repodir = os.path.dirname(repodir)
 2916             if os.path.splitdrive(repodir)[1] == os.sep:
 2917                 return False
 2918         
 2919         args = self.initCommand("graft")
 2920         args.append(subcommand)
 2921         args.append("--verbose")
 2922         
 2923         dia = HgDialog(title, self)
 2924         res = dia.startProcess(args, repodir)
 2925         if res:
 2926             dia.exec_()
 2927             res = dia.hasAddOrDelete()
 2928             self.checkVCSStatus()
 2929         return res
 2930     
 2931     def hgGraftContinue(self, path):
 2932         """
 2933         Public method to continue copying changesets from another branch.
 2934         
 2935         @param path directory name of the project
 2936         @type str
 2937         @return flag indicating that the project should be reread
 2938         @rtype bool
 2939         """
 2940         return self.__hgGraftSubCommand(
 2941             path, "--continue", self.tr('Copy Changesets (Continue)'))
 2942     
 2943     def hgGraftStop(self, path):
 2944         """
 2945         Public method to stop an interrupted copying session.
 2946         
 2947         @param path directory name of the project
 2948         @type str
 2949         @return flag indicating that the project should be reread
 2950         @rtype bool
 2951         """
 2952         return self.__hgGraftSubCommand(
 2953             path, "--stop", self.tr('Copy Changesets (Stop)'))
 2954     
 2955     def hgGraftAbort(self, path):
 2956         """
 2957         Public method to abort an interrupted copying session and perform
 2958         a rollback.
 2959         
 2960         @param path directory name of the project
 2961         @type str
 2962         @return flag indicating that the project should be reread
 2963         @rtype bool
 2964         """
 2965         return self.__hgGraftSubCommand(
 2966             path, "--abort", self.tr('Copy Changesets (Abort)'))
 2967     
 2968     def hgArchive(self):
 2969         """
 2970         Public method to create an unversioned archive from the repository.
 2971         """
 2972         # find the root of the repo
 2973         repodir = self.__projectHelper.getProject().getProjectPath()
 2974         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 2975             repodir = os.path.dirname(repodir)
 2976             if os.path.splitdrive(repodir)[1] == os.sep:
 2977                 return
 2978         
 2979         from .HgArchiveDialog import HgArchiveDialog
 2980         dlg = HgArchiveDialog(self)
 2981         if dlg.exec_() == QDialog.Accepted:
 2982             archive, type_, prefix, subrepos = dlg.getData()
 2983             
 2984             args = self.initCommand("archive")
 2985             if type_:
 2986                 args.append("--type")
 2987                 args.append(type_)
 2988             if prefix:
 2989                 args.append("--prefix")
 2990                 args.append(prefix)
 2991             if subrepos:
 2992                 args.append("--subrepos")
 2993             args.append(archive)
 2994             
 2995             dia = HgDialog(self.tr("Create Unversioned Archive"), self)
 2996             res = dia.startProcess(args, repodir)
 2997             if res:
 2998                 dia.exec_()
 2999     
 3000     def hgDeleteBackups(self):
 3001         """
 3002         Public method to delete all backup bundles in the backup area.
 3003         """
 3004         # find the root of the repo
 3005         repodir = self.__projectHelper.getProject().getProjectPath()
 3006         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 3007             repodir = os.path.dirname(repodir)
 3008             if os.path.splitdrive(repodir)[1] == os.sep:
 3009                 return
 3010         
 3011         backupdir = os.path.join(repodir, self.adminDir, "strip-backup")
 3012         yes = E5MessageBox.yesNo(
 3013             self.__ui,
 3014             self.tr("Delete All Backups"),
 3015             self.tr("""<p>Do you really want to delete all backup bundles"""
 3016                     """ stored the backup area <b>{0}</b>?</p>""").format(
 3017                 backupdir))
 3018         if yes:
 3019             shutil.rmtree(backupdir, True)
 3020     
 3021     ###########################################################################
 3022     ## Methods to deal with subrepositories are below.
 3023     ###########################################################################
 3024     
 3025     def getHgSubPath(self):
 3026         """
 3027         Public method to get the path to the .hgsub file containing the
 3028         definitions of sub-repositories.
 3029         
 3030         @return full path of the .hgsub file (string)
 3031         """
 3032         ppath = self.__projectHelper.getProject().getProjectPath()
 3033         return os.path.join(ppath, ".hgsub")
 3034     
 3035     def hasSubrepositories(self):
 3036         """
 3037         Public method to check, if the project might have sub-repositories.
 3038         
 3039         @return flag indicating the existence of sub-repositories (boolean)
 3040         """
 3041         hgsub = self.getHgSubPath()
 3042         return os.path.isfile(hgsub) and os.stat(hgsub).st_size > 0
 3043     
 3044     def hgAddSubrepository(self):
 3045         """
 3046         Public method to add a sub-repository.
 3047         """
 3048         from .HgAddSubrepositoryDialog import HgAddSubrepositoryDialog
 3049         ppath = self.__projectHelper.getProject().getProjectPath()
 3050         hgsub = self.getHgSubPath()
 3051         dlg = HgAddSubrepositoryDialog(ppath)
 3052         if dlg.exec_() == QDialog.Accepted:
 3053             relPath, subrepoType, subrepoUrl = dlg.getData()
 3054             if subrepoType == "hg":
 3055                 url = subrepoUrl
 3056             else:
 3057                 url = "[{0}]{1}".format(subrepoType, subrepoUrl)
 3058             entry = "{0} = {1}\n".format(relPath, url)
 3059             
 3060             contents = []
 3061             if os.path.isfile(hgsub):
 3062                 # file exists; check, if such an entry exists already
 3063                 needsAdd = False
 3064                 try:
 3065                     f = open(hgsub, "r")
 3066                     contents = f.readlines()
 3067                     f.close()
 3068                 except IOError as err:
 3069                     E5MessageBox.critical(
 3070                         self.__ui,
 3071                         self.tr("Add Sub-repository"),
 3072                         self.tr(
 3073                             """<p>The sub-repositories file .hgsub could not"""
 3074                             """ be read.</p><p>Reason: {0}</p>""")
 3075                         .format(str(err)))
 3076                     return
 3077                 
 3078                 if entry in contents:
 3079                     E5MessageBox.critical(
 3080                         self.__ui,
 3081                         self.tr("Add Sub-repository"),
 3082                         self.tr(
 3083                             """<p>The sub-repositories file .hgsub already"""
 3084                             """ contains an entry <b>{0}</b>."""
 3085                             """ Aborting...</p>""").format(entry))
 3086                     return
 3087             else:
 3088                 needsAdd = True
 3089             
 3090             if contents and not contents[-1].endswith("\n"):
 3091                 contents[-1] = contents[-1] + "\n"
 3092             contents.append(entry)
 3093             try:
 3094                 f = open(hgsub, "w")
 3095                 f.writelines(contents)
 3096                 f.close()
 3097             except IOError as err:
 3098                 E5MessageBox.critical(
 3099                     self.__ui,
 3100                     self.tr("Add Sub-repository"),
 3101                     self.tr(
 3102                         """<p>The sub-repositories file .hgsub could not"""
 3103                         """ be written to.</p><p>Reason: {0}</p>""")
 3104                     .format(str(err)))
 3105                 return
 3106             
 3107             if needsAdd:
 3108                 self.vcsAdd(hgsub)
 3109                 self.__projectHelper.getProject().appendFile(hgsub)
 3110     
 3111     def hgRemoveSubrepositories(self):
 3112         """
 3113         Public method to remove sub-repositories.
 3114         """
 3115         hgsub = self.getHgSubPath()
 3116         
 3117         subrepositories = []
 3118         if not os.path.isfile(hgsub):
 3119             E5MessageBox.critical(
 3120                 self.__ui,
 3121                 self.tr("Remove Sub-repositories"),
 3122                 self.tr("""<p>The sub-repositories file .hgsub does not"""
 3123                         """ exist. Aborting...</p>"""))
 3124             return
 3125             
 3126         try:
 3127             f = open(hgsub, "r")
 3128             subrepositories = [line.strip() for line in f.readlines()]
 3129             f.close()
 3130         except IOError as err:
 3131             E5MessageBox.critical(
 3132                 self.__ui,
 3133                 self.tr("Remove Sub-repositories"),
 3134                 self.tr("""<p>The sub-repositories file .hgsub could not"""
 3135                         """ be read.</p><p>Reason: {0}</p>""")
 3136                 .format(str(err)))
 3137             return
 3138         
 3139         from .HgRemoveSubrepositoriesDialog import (
 3140             HgRemoveSubrepositoriesDialog
 3141         )
 3142         dlg = HgRemoveSubrepositoriesDialog(subrepositories)
 3143         if dlg.exec_() == QDialog.Accepted:
 3144             subrepositories, removedSubrepos, deleteSubrepos = dlg.getData()
 3145             contents = "\n".join(subrepositories) + "\n"
 3146             try:
 3147                 f = open(hgsub, "w")
 3148                 f.write(contents)
 3149                 f.close()
 3150             except IOError as err:
 3151                 E5MessageBox.critical(
 3152                     self.__ui,
 3153                     self.tr("Remove Sub-repositories"),
 3154                     self.tr(
 3155                         """<p>The sub-repositories file .hgsub could not"""
 3156                         """ be written to.</p><p>Reason: {0}</p>""")
 3157                     .format(str(err)))
 3158                 return
 3159             
 3160             if deleteSubrepos:
 3161                 ppath = self.__projectHelper.getProject().getProjectPath()
 3162                 for removedSubrepo in removedSubrepos:
 3163                     subrepoPath = removedSubrepo.split("=", 1)[0].strip()
 3164                     subrepoAbsPath = os.path.join(ppath, subrepoPath)
 3165                     shutil.rmtree(subrepoAbsPath, True)
 3166     
 3167     ###########################################################################
 3168     ## Methods to handle configuration dependent stuff are below.
 3169     ###########################################################################
 3170     
 3171     def __checkDefaults(self):
 3172         """
 3173         Private method to check, if the default and default-push URLs
 3174         have been configured.
 3175         """
 3176         args = self.initCommand("showconfig")
 3177         args.append('paths')
 3178         
 3179         output, error = self.__client.runcommand(args)
 3180         
 3181         self.__defaultConfigured = False
 3182         self.__defaultPushConfigured = False
 3183         if output:
 3184             for line in output.splitlines():
 3185                 line = line.strip()
 3186                 if (
 3187                     line.startswith("paths.default=") and
 3188                     not line.endswith("=")
 3189                 ):
 3190                     self.__defaultConfigured = True
 3191                 if (
 3192                     line.startswith("paths.default-push=") and
 3193                     not line.endswith("=")
 3194                 ):
 3195                     self.__defaultPushConfigured = True
 3196     
 3197     def canCommitMerge(self, name):
 3198         """
 3199         Public method to check, if the working directory is an uncommitted
 3200         merge.
 3201  
 3202         @param name file/directory name
 3203         @type str
 3204         @return flag indicating commit merge capability
 3205         @rtype bool
 3206         """
 3207         dname, fname = self.splitPath(name)
 3208         
 3209         # find the root of the repo
 3210         repodir = dname
 3211         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 3212             repodir = os.path.dirname(repodir)
 3213             if os.path.splitdrive(repodir)[1] == os.sep:
 3214                 return False
 3215         
 3216         args = self.initCommand("identify")
 3217         
 3218         output, error = self.__client.runcommand(args)
 3219         
 3220         return output.count('+') == 2
 3221 
 3222     def canPull(self):
 3223         """
 3224         Public method to check, if pull is possible.
 3225         
 3226         @return flag indicating pull capability (boolean)
 3227         """
 3228         return self.__defaultConfigured
 3229     
 3230     def canPush(self):
 3231         """
 3232         Public method to check, if push is possible.
 3233         
 3234         @return flag indicating push capability (boolean)
 3235         """
 3236         return self.__defaultPushConfigured or self.__defaultConfigured
 3237     
 3238     def __iniFileChanged(self, path):
 3239         """
 3240         Private slot to handle a change of the Mercurial configuration file.
 3241         
 3242         @param path name of the changed file (string)
 3243         """
 3244         if self.__client:
 3245             ok, err = self.__client.restartServer()
 3246             if not ok:
 3247                 E5MessageBox.warning(
 3248                     None,
 3249                     self.tr("Mercurial Command Server"),
 3250                     self.tr(
 3251                         """<p>The Mercurial Command Server could not be"""
 3252                         """ restarted.</p><p>Reason: {0}</p>""").format(err))
 3253         
 3254         self.__getExtensionsInfo()
 3255         
 3256         if self.__repoIniFile and path == self.__repoIniFile:
 3257             self.__checkDefaults()
 3258         
 3259         self.iniFileChanged.emit()
 3260     
 3261     def __monitorRepoIniFile(self, name):
 3262         """
 3263         Private slot to add a repository configuration file to the list of
 3264         monitored files.
 3265         
 3266         @param name directory name pointing into the repository (string)
 3267         """
 3268         dname, fname = self.splitPath(name)
 3269         
 3270         # find the root of the repo
 3271         repodir = dname
 3272         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 3273             repodir = os.path.dirname(repodir)
 3274             if not repodir or os.path.splitdrive(repodir)[1] == os.sep:
 3275                 return
 3276         
 3277         cfgFile = os.path.join(repodir, self.adminDir, "hgrc")
 3278         if os.path.exists(cfgFile):
 3279             self.__iniWatcher.addPath(cfgFile)
 3280             self.__repoIniFile = cfgFile
 3281             self.__checkDefaults()
 3282     
 3283     ###########################################################################
 3284     ## Methods to handle extensions are below.
 3285     ###########################################################################
 3286     
 3287     def __getExtensionsInfo(self):
 3288         """
 3289         Private method to get the active extensions from Mercurial.
 3290         """
 3291         activeExtensions = sorted(self.__activeExtensions)
 3292         self.__activeExtensions = []
 3293         
 3294         args = self.initCommand("showconfig")
 3295         args.append('extensions')
 3296         
 3297         output, error = self.__client.runcommand(args)
 3298         
 3299         if output:
 3300             for line in output.splitlines():
 3301                 extensionName = (
 3302                     line.split("=", 1)[0].strip().split(".")[-1].strip()
 3303                 )
 3304                 self.__activeExtensions.append(extensionName)
 3305         if self.version < (4, 8, 0) and "closehead" in self.__activeExtensions:
 3306             self.__activeExtensions.remove["closehead"]
 3307         
 3308         if activeExtensions != sorted(self.__activeExtensions):
 3309             self.activeExtensionsChanged.emit()
 3310     
 3311     def isExtensionActive(self, extensionName):
 3312         """
 3313         Public method to check, if an extension is active.
 3314         
 3315         @param extensionName name of the extension to check for (string)
 3316         @return flag indicating an active extension (boolean)
 3317         """
 3318         extensionName = extensionName.strip()
 3319         isActive = extensionName in self.__activeExtensions
 3320         
 3321         return isActive
 3322     
 3323     def getExtensionObject(self, extensionName):
 3324         """
 3325         Public method to get a reference to an extension object.
 3326         
 3327         @param extensionName name of the extension (string)
 3328         @return reference to the extension object (boolean)
 3329         """
 3330         return self.__extensions[extensionName]
 3331     
 3332     ###########################################################################
 3333     ## Methods to get the helper objects are below.
 3334     ###########################################################################
 3335     
 3336     def vcsGetProjectBrowserHelper(self, browser, project,
 3337                                    isTranslationsBrowser=False):
 3338         """
 3339         Public method to instantiate a helper object for the different
 3340         project browsers.
 3341         
 3342         @param browser reference to the project browser object
 3343         @param project reference to the project object
 3344         @param isTranslationsBrowser flag indicating, the helper is requested
 3345             for the translations browser (this needs some special treatment)
 3346         @return the project browser helper object
 3347         """
 3348         from .ProjectBrowserHelper import HgProjectBrowserHelper
 3349         return HgProjectBrowserHelper(self, browser, project,
 3350                                       isTranslationsBrowser)
 3351         
 3352     def vcsGetProjectHelper(self, project):
 3353         """
 3354         Public method to instantiate a helper object for the project.
 3355         
 3356         @param project reference to the project object
 3357         @return the project helper object
 3358         """
 3359         # find the root of the repo
 3360         repodir = project.getProjectPath()
 3361         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 3362             repodir = os.path.dirname(repodir)
 3363             if not repodir or os.path.splitdrive(repodir)[1] == os.sep:
 3364                 repodir = ""
 3365                 break
 3366         
 3367         self.__projectHelper = self.__plugin.getProjectHelper()
 3368         self.__projectHelper.setObjects(self, project)
 3369         
 3370         if repodir:
 3371             self.__repoDir = repodir
 3372             self.__createClient(repodir)
 3373             self.__monitorRepoIniFile(repodir)
 3374         
 3375         return self.__projectHelper
 3376     
 3377     ###########################################################################
 3378     ## Methods to handle the Mercurial command server are below.
 3379     ###########################################################################
 3380     
 3381     def __createClient(self, repodir=""):
 3382         """
 3383         Private method to create a Mercurial command server client.
 3384         
 3385         @param repodir path of the local repository
 3386         @type str
 3387         """
 3388         if self.__client is not None:
 3389             self.__client.stopServer()
 3390             self.__client = None
 3391         
 3392         self.__client = HgClient(repodir, "utf-8", self)
 3393         ok, err = self.__client.startServer()
 3394         if not ok:
 3395             E5MessageBox.warning(
 3396                 None,
 3397                 self.tr("Mercurial Command Server"),
 3398                 self.tr(
 3399                     """<p>The Mercurial Command Server could not be"""
 3400                     """ started.</p><p>Reason: {0}</p>""").format(err))
 3401     
 3402     def getClient(self):
 3403         """
 3404         Public method to get a reference to the command server interface.
 3405         
 3406         @return reference to the client (HgClient)
 3407         """
 3408         if self.__client is None:
 3409             self.__createClient(self.__repoDir)
 3410         
 3411         return self.__client
 3412     
 3413     ###########################################################################
 3414     ##  Status Monitor Thread methods
 3415     ###########################################################################
 3416 
 3417     def _createStatusMonitorThread(self, interval, project):
 3418         """
 3419         Protected method to create an instance of the VCS status monitor
 3420         thread.
 3421         
 3422         @param interval check interval for the monitor thread in seconds
 3423             (integer)
 3424         @param project reference to the project object (Project)
 3425         @return reference to the monitor thread (QThread)
 3426         """
 3427         from .HgStatusMonitorThread import HgStatusMonitorThread
 3428         return HgStatusMonitorThread(interval, project, self)
 3429 
 3430     ###########################################################################
 3431     ##  Bookmarks methods
 3432     ###########################################################################
 3433 
 3434     def hgListBookmarks(self, path):
 3435         """
 3436         Public method used to list the available bookmarks.
 3437         
 3438         @param path directory name of the project (string)
 3439         """
 3440         self.bookmarksList = []
 3441         
 3442         if self.bookmarksListDlg is None:
 3443             from .HgBookmarksListDialog import HgBookmarksListDialog
 3444             self.bookmarksListDlg = HgBookmarksListDialog(self)
 3445         self.bookmarksListDlg.show()
 3446         self.bookmarksListDlg.raise_()
 3447         self.bookmarksListDlg.start(path, self.bookmarksList)
 3448     
 3449     def hgGetBookmarksList(self, repodir):
 3450         """
 3451         Public method to get the list of bookmarks.
 3452         
 3453         @param repodir directory name of the repository (string)
 3454         @return list of bookmarks (list of string)
 3455         """
 3456         args = self.initCommand("bookmarks")
 3457         
 3458         client = self.getClient()
 3459         output = ""
 3460         if client:
 3461             output = client.runcommand(args)[0]
 3462         else:
 3463             process = QProcess()
 3464             process.setWorkingDirectory(repodir)
 3465             process.start('hg', args)
 3466             procStarted = process.waitForStarted(5000)
 3467             if procStarted:
 3468                 finished = process.waitForFinished(30000)
 3469                 if finished and process.exitCode() == 0:
 3470                     output = str(process.readAllStandardOutput(),
 3471                                  self.getEncoding(), 'replace')
 3472         
 3473         self.bookmarksList = []
 3474         for line in output.splitlines():
 3475             li = line.strip().split()
 3476             if li[-1][0] in "1234567890":
 3477                 # last element is a rev:changeset
 3478                 del li[-1]
 3479                 if li[0] == "*":
 3480                     del li[0]
 3481                 name = " ".join(li)
 3482                 self.bookmarksList.append(name)
 3483         
 3484         return self.bookmarksList[:]
 3485     
 3486     def hgBookmarkDefine(self, name, revision=None, bookmark=None):
 3487         """
 3488         Public method to define a bookmark.
 3489         
 3490         @param name file/directory name (string)
 3491         @param revision revision to set bookmark for (string)
 3492         @param bookmark name of the bookmark (string)
 3493         """
 3494         # find the root of the repo
 3495         repodir = self.splitPath(name)[0]
 3496         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 3497             repodir = os.path.dirname(repodir)
 3498             if os.path.splitdrive(repodir)[1] == os.sep:
 3499                 return
 3500         
 3501         if bool(revision) and bool(bookmark):
 3502             ok = True
 3503         else:
 3504             from .HgBookmarkDialog import HgBookmarkDialog
 3505             dlg = HgBookmarkDialog(HgBookmarkDialog.DEFINE_MODE,
 3506                                    self.hgGetTagsList(repodir),
 3507                                    self.hgGetBranchesList(repodir),
 3508                                    self.hgGetBookmarksList(repodir))
 3509             if dlg.exec_() == QDialog.Accepted:
 3510                 revision, bookmark = dlg.getData()
 3511                 ok = True
 3512             else:
 3513                 ok = False
 3514         
 3515         if ok:
 3516             args = self.initCommand("bookmarks")
 3517             if revision:
 3518                 args.append("--rev")
 3519                 args.append(revision)
 3520             args.append(bookmark)
 3521             
 3522             dia = HgDialog(self.tr('Mercurial Bookmark'), self)
 3523             res = dia.startProcess(args, repodir)
 3524             if res:
 3525                 dia.exec_()
 3526     
 3527     def hgBookmarkDelete(self, name, bookmark=None):
 3528         """
 3529         Public method to delete a bookmark.
 3530         
 3531         @param name file/directory name (string)
 3532         @param bookmark name of the bookmark (string)
 3533         """
 3534         # find the root of the repo
 3535         repodir = self.splitPath(name)[0]
 3536         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 3537             repodir = os.path.dirname(repodir)
 3538             if os.path.splitdrive(repodir)[1] == os.sep:
 3539                 return
 3540         
 3541         if bookmark:
 3542             ok = True
 3543         else:
 3544             bookmark, ok = QInputDialog.getItem(
 3545                 None,
 3546                 self.tr("Delete Bookmark"),
 3547                 self.tr("Select the bookmark to be deleted:"),
 3548                 [""] + sorted(self.hgGetBookmarksList(repodir)),
 3549                 0, True)
 3550         if ok and bookmark:
 3551             args = self.initCommand("bookmarks")
 3552             args.append("--delete")
 3553             args.append(bookmark)
 3554             
 3555             dia = HgDialog(self.tr('Delete Mercurial Bookmark'), self)
 3556             res = dia.startProcess(args, repodir)
 3557             if res:
 3558                 dia.exec_()
 3559     
 3560     def hgBookmarkRename(self, name, renameInfo=None):
 3561         """
 3562         Public method to rename a bookmark.
 3563         
 3564         @param name file/directory name
 3565         @type str
 3566         @param renameInfo old and new names of the bookmark
 3567         @type tuple of str and str
 3568         """
 3569         # find the root of the repo
 3570         repodir = self.splitPath(name)[0]
 3571         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 3572             repodir = os.path.dirname(repodir)
 3573             if os.path.splitdrive(repodir)[1] == os.sep:
 3574                 return
 3575         
 3576         if not renameInfo:
 3577             from .HgBookmarkRenameDialog import HgBookmarkRenameDialog
 3578             dlg = HgBookmarkRenameDialog(self.hgGetBookmarksList(repodir))
 3579             if dlg.exec_() == QDialog.Accepted:
 3580                 renameInfo = dlg.getData()
 3581         
 3582         if renameInfo:
 3583             args = self.initCommand("bookmarks")
 3584             args.append("--rename")
 3585             args.append(renameInfo[0])
 3586             args.append(renameInfo[1])
 3587             
 3588             dia = HgDialog(self.tr('Rename Mercurial Bookmark'), self)
 3589             res = dia.startProcess(args, repodir)
 3590             if res:
 3591                 dia.exec_()
 3592     
 3593     def hgBookmarkMove(self, name, revision=None, bookmark=None):
 3594         """
 3595         Public method to move a bookmark.
 3596         
 3597         @param name file/directory name (string)
 3598         @param revision revision to set bookmark for (string)
 3599         @param bookmark name of the bookmark (string)
 3600         """
 3601         # find the root of the repo
 3602         repodir = self.splitPath(name)[0]
 3603         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 3604             repodir = os.path.dirname(repodir)
 3605             if os.path.splitdrive(repodir)[1] == os.sep:
 3606                 return
 3607         
 3608         if bool(revision) and bool(bookmark):
 3609             ok = True
 3610         else:
 3611             from .HgBookmarkDialog import HgBookmarkDialog
 3612             dlg = HgBookmarkDialog(HgBookmarkDialog.MOVE_MODE,
 3613                                    self.hgGetTagsList(repodir),
 3614                                    self.hgGetBranchesList(repodir),
 3615                                    self.hgGetBookmarksList(repodir))
 3616             if dlg.exec_() == QDialog.Accepted:
 3617                 revision, bookmark = dlg.getData()
 3618                 ok = True
 3619             else:
 3620                 ok = False
 3621         
 3622         if ok:
 3623             args = self.initCommand("bookmarks")
 3624             args.append("--force")
 3625             if revision:
 3626                 args.append("--rev")
 3627                 args.append(revision)
 3628             args.append(bookmark)
 3629             
 3630             dia = HgDialog(self.tr('Move Mercurial Bookmark'), self)
 3631             res = dia.startProcess(args, repodir)
 3632             if res:
 3633                 dia.exec_()
 3634     
 3635     def hgBookmarkIncoming(self, name):
 3636         """
 3637         Public method to show a list of incoming bookmarks.
 3638         
 3639         @param name file/directory name (string)
 3640         """
 3641         from .HgBookmarksInOutDialog import HgBookmarksInOutDialog
 3642         self.bookmarksInOutDlg = HgBookmarksInOutDialog(
 3643             self, HgBookmarksInOutDialog.INCOMING)
 3644         self.bookmarksInOutDlg.show()
 3645         self.bookmarksInOutDlg.start(name)
 3646     
 3647     def hgBookmarkOutgoing(self, name):
 3648         """
 3649         Public method to show a list of outgoing bookmarks.
 3650         
 3651         @param name file/directory name (string)
 3652         """
 3653         from .HgBookmarksInOutDialog import HgBookmarksInOutDialog
 3654         self.bookmarksInOutDlg = HgBookmarksInOutDialog(
 3655             self, HgBookmarksInOutDialog.OUTGOING)
 3656         self.bookmarksInOutDlg.show()
 3657         self.bookmarksInOutDlg.start(name)
 3658     
 3659     def __getInOutBookmarks(self, repodir, incoming):
 3660         """
 3661         Private method to get the list of incoming or outgoing bookmarks.
 3662         
 3663         @param repodir directory name of the repository (string)
 3664         @param incoming flag indicating to get incoming bookmarks (boolean)
 3665         @return list of bookmarks (list of string)
 3666         """
 3667         bookmarksList = []
 3668         
 3669         if incoming:
 3670             args = self.initCommand("incoming")
 3671         else:
 3672             args = self.initCommand("outgoing")
 3673         args.append('--bookmarks')
 3674         
 3675         client = self.getClient()
 3676         output = ""
 3677         if client:
 3678             output = client.runcommand(args)[0]
 3679         else:
 3680             process = QProcess()
 3681             process.setWorkingDirectory(repodir)
 3682             process.start('hg', args)
 3683             procStarted = process.waitForStarted(5000)
 3684             if procStarted:
 3685                 finished = process.waitForFinished(30000)
 3686                 if finished and process.exitCode() == 0:
 3687                     output = str(process.readAllStandardOutput(),
 3688                                  self.getEncoding(), 'replace')
 3689         
 3690         for line in output.splitlines():
 3691             if line.startswith(" "):
 3692                 li = line.strip().split()
 3693                 del li[-1]
 3694                 name = " ".join(li)
 3695                 bookmarksList.append(name)
 3696         
 3697         return bookmarksList
 3698     
 3699     def hgBookmarkPull(self, name, current=False, bookmark=None):
 3700         """
 3701         Public method to pull a bookmark from a remote repository.
 3702         
 3703         @param name file/directory name
 3704         @type str
 3705         @param current flag indicating to pull the current bookmark
 3706         @type bool
 3707         @param bookmark name of the bookmark
 3708         @type str
 3709         """
 3710         # find the root of the repo
 3711         repodir = self.splitPath(name)[0]
 3712         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 3713             repodir = os.path.dirname(repodir)
 3714             if os.path.splitdrive(repodir)[1] == os.sep:
 3715                 return
 3716         
 3717         if current:
 3718             bookmark = "."
 3719             ok = True
 3720         elif bookmark:
 3721             ok = True
 3722         else:
 3723             bookmarks = self.__getInOutBookmarks(repodir, True)
 3724             bookmark, ok = QInputDialog.getItem(
 3725                 None,
 3726                 self.tr("Pull Bookmark"),
 3727                 self.tr("Select the bookmark to be pulled:"),
 3728                 [""] + sorted(bookmarks),
 3729                 0, True)
 3730         
 3731         if ok and bookmark:
 3732             args = self.initCommand("pull")
 3733             args.append('--bookmark')
 3734             args.append(bookmark)
 3735             
 3736             dia = HgDialog(self.tr(
 3737                 'Pulling bookmark from a remote Mercurial repository'),
 3738                 self)
 3739             res = dia.startProcess(args, repodir)
 3740             if res:
 3741                 dia.exec_()
 3742     
 3743     def hgBookmarkPush(self, name, current=False, bookmark=None):
 3744         """
 3745         Public method to push a bookmark to a remote repository.
 3746         
 3747         @param name file/directory name
 3748         @type str
 3749         @param current flag indicating to push the current bookmark
 3750         @type bool
 3751         @param bookmark name of the bookmark
 3752         @type str
 3753         """
 3754         # find the root of the repo
 3755         repodir = self.splitPath(name)[0]
 3756         while not os.path.isdir(os.path.join(repodir, self.adminDir)):
 3757             repodir = os.path.dirname(repodir)
 3758             if os.path.splitdrive(repodir)[1] == os.sep:
 3759                 return
 3760         
 3761         if current:
 3762             bookmark = "."
 3763             ok = True
 3764         elif bookmark:
 3765             ok = True
 3766         else:
 3767             bookmarks = self.__getInOutBookmarks(repodir, False)
 3768             bookmark, ok = QInputDialog.getItem(
 3769                 None,
 3770                 self.tr("Push Bookmark"),
 3771                 self.tr("Select the bookmark to be push:"),
 3772                 [""] + sorted(bookmarks),
 3773                 0, True)
 3774         
 3775         if ok and bookmark:
 3776             args = self.initCommand("push")
 3777             args.append('--bookmark')
 3778             args.append(bookmark)
 3779             
 3780             dia = HgDialog(self.tr(
 3781                 'Pushing bookmark to a remote Mercurial repository'),
 3782                 self)
 3783             res = dia.startProcess(args, repodir)
 3784             if res:
 3785                 dia.exec_()