src/eric7/Plugins/VcsPlugins/vcsMercurial/hg.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9153
506e35e424d5
child 9221
bf71ee032bb4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Plugins/VcsPlugins/vcsMercurial/hg.py	Thu Jul 07 11:23:56 2022 +0200
@@ -0,0 +1,3313 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2010 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the version control systems interface to Mercurial.
+"""
+
+import os
+import shutil
+import contextlib
+import pathlib
+
+from PyQt6.QtCore import pyqtSignal, QFileSystemWatcher, QCoreApplication
+from PyQt6.QtWidgets import QApplication, QDialog, QInputDialog
+
+from EricWidgets.EricApplication import ericApp
+from EricWidgets import EricMessageBox, EricFileDialog
+
+from QScintilla.MiniEditor import MiniEditor
+
+from VCS.VersionControl import VersionControl
+from VCS.RepositoryInfoDialog import VcsRepositoryInfoDialog
+
+from .HgDialog import HgDialog
+from .HgClient import HgClient
+
+import Preferences
+import Utilities
+
+
+class Hg(VersionControl):
+    """
+    Class implementing the version control systems interface to Mercurial.
+    
+    @signal committed() emitted after the commit action has completed
+    @signal activeExtensionsChanged() emitted when the list of active
+        extensions has changed
+    @signal iniFileChanged() emitted when a Mercurial/repo configuration file
+        has changed
+    """
+    committed = pyqtSignal()
+    activeExtensionsChanged = pyqtSignal()
+    iniFileChanged = pyqtSignal()
+    
+    IgnoreFileName = ".hgignore"
+    
+    def __init__(self, plugin, parent=None, name=None):
+        """
+        Constructor
+        
+        @param plugin reference to the plugin object
+        @param parent parent widget (QWidget)
+        @param name name of this object (string)
+        """
+        VersionControl.__init__(self, parent, name)
+        self.defaultOptions = {
+            'global': [''],
+            'commit': [''],
+            'checkout': [''],
+            'update': [''],
+            'add': [''],
+            'remove': [''],
+            'diff': [''],
+            'log': [''],
+            'history': [''],
+            'status': [''],
+            'tag': [''],
+            'export': ['']
+        }
+        
+        self.__plugin = plugin
+        self.__ui = parent
+        
+        self.options = self.defaultOptions
+        self.tagsList = []
+        self.branchesList = []
+        self.allTagsBranchesList = []
+        self.bookmarksList = []
+        self.showedTags = False
+        self.showedBranches = False
+        
+        self.tagTypeList = [
+            'tags',
+            'branches',
+        ]
+        
+        self.commandHistory = []
+        
+        if "HG_ASP_DOT_NET_HACK" in os.environ:
+            self.adminDir = '_hg'
+        else:
+            self.adminDir = '.hg'
+        
+        self.logBrowser = None
+        self.logBrowserIncoming = None
+        self.logBrowserOutgoing = None
+        self.diff = None
+        self.sbsDiff = None
+        self.status = None
+        self.summary = None
+        self.tagbranchList = None
+        self.annotate = None
+        self.repoEditor = None
+        self.serveDlg = None
+        self.bookmarksListDlg = None
+        self.bookmarksInOutDlg = None
+        self.conflictsDlg = None
+        
+        self.bundleFile = None
+        self.__lastChangeGroupPath = None
+        
+        self.statusCache = {}
+        
+        self.__commitData = {}
+        self.__commitDialog = None
+        
+        self.__forgotNames = []
+        
+        self.__activeExtensions = []
+        
+        from .HgUtilities import getConfigPath
+        self.__iniWatcher = QFileSystemWatcher(self)
+        self.__iniWatcher.fileChanged.connect(self.__iniFileChanged)
+        cfgFile = getConfigPath()
+        if os.path.exists(cfgFile):
+            self.__iniWatcher.addPath(cfgFile)
+        
+        self.__client = None
+        self.__createClient()
+        self.__projectHelper = None
+        
+        self.__repoDir = ""
+        self.__repoIniFile = ""
+        self.__defaultConfigured = False
+        self.__defaultPushConfigured = False
+        
+        # instantiate the extensions
+        from .QueuesExtension.queues import Queues
+        from .PurgeExtension.purge import Purge
+        from .GpgExtension.gpg import Gpg
+        from .RebaseExtension.rebase import Rebase
+        from .ShelveExtension.shelve import Shelve
+        from .LargefilesExtension.largefiles import Largefiles
+        from .StripExtension.strip import Strip
+        from .HisteditExtension.histedit import Histedit
+        from .CloseheadExtension.closehead import Closehead
+        self.__extensions = {
+            "mq": Queues(self),
+            "purge": Purge(self),
+            "gpg": Gpg(self),
+            "rebase": Rebase(self),
+            "shelve": Shelve(self),
+            "largefiles": Largefiles(self),
+            "strip": Strip(self),
+            "histedit": Histedit(self),
+            "closehead": Closehead(self),
+        }
+    
+    def getPlugin(self):
+        """
+        Public method to get a reference to the plugin object.
+        
+        @return reference to the plugin object (VcsMercurialPlugin)
+        """
+        return self.__plugin
+    
+    def getEncoding(self):
+        """
+        Public method to get the encoding to be used by Mercurial.
+        
+        @return encoding (string)
+        """
+        return self.__plugin.getPreferences("Encoding")
+    
+    def vcsShutdown(self):
+        """
+        Public method used to shutdown the Mercurial interface.
+        """
+        if self.logBrowser is not None:
+            self.logBrowser.close()
+        if self.logBrowserIncoming is not None:
+            self.logBrowserIncoming.close()
+        if self.logBrowserOutgoing is not None:
+            self.logBrowserOutgoing.close()
+        if self.diff is not None:
+            self.diff.close()
+        if self.sbsDiff is not None:
+            self.sbsDiff.close()
+        if self.status is not None:
+            self.status.close()
+        if self.summary is not None:
+            self.summary.close()
+        if self.tagbranchList is not None:
+            self.tagbranchList.close()
+        if self.annotate is not None:
+            self.annotate.close()
+        if self.serveDlg is not None:
+            self.serveDlg.close()
+        
+        if self.bookmarksListDlg is not None:
+            self.bookmarksListDlg.close()
+        if self.bookmarksInOutDlg is not None:
+            self.bookmarksInOutDlg.close()
+        
+        if self.conflictsDlg is not None:
+            self.conflictsDlg.close()
+        
+        if self.bundleFile and os.path.exists(self.bundleFile):
+            os.remove(self.bundleFile)
+        
+        # shut down the project helpers
+        if self.__projectHelper is not None:
+            self.__projectHelper.shutdown()
+        
+        # shut down the extensions
+        for extension in self.__extensions.values():
+            extension.shutdown()
+        
+        # shut down the client
+        self.__client and self.__client.stopServer()
+    
+    def initCommand(self, command):
+        """
+        Public method to initialize a command arguments list.
+        
+        @param command command name (string)
+        @return list of command options (list of string)
+        """
+        args = [command]
+        self.addArguments(args, self.__plugin.getGlobalOptions())
+        return args
+    
+    def vcsExists(self):
+        """
+        Public method used to test for the presence of the hg executable.
+        
+        @return flag indicating the existence (boolean) and an error message
+            (string)
+        """
+        from .HgUtilities import hgVersion
+        
+        self.versionStr, self.version, errMsg = hgVersion(self.__plugin)
+        hgExists = errMsg == ""
+        if hgExists:
+            self.__getExtensionsInfo()
+        return hgExists, errMsg
+    
+    def vcsInit(self, vcsDir, noDialog=False):
+        """
+        Public method used to initialize the mercurial repository.
+        
+        The initialization is done, when a project is converted into a
+        Mercurial controlled project. Therefore we always return TRUE without
+        doing anything.
+        
+        @param vcsDir name of the VCS directory (string)
+        @param noDialog flag indicating quiet operations (boolean)
+        @return always TRUE
+        """
+        return True
+    
+    def vcsConvertProject(self, vcsDataDict, project, addAll=True):
+        """
+        Public method to convert an uncontrolled project to a version
+        controlled project.
+        
+        @param vcsDataDict dictionary of data required for the conversion
+        @type dict
+        @param project reference to the project object
+        @type Project
+        @param addAll flag indicating to add all files to the repository
+        @type bool
+        """
+        success = self.vcsImport(vcsDataDict, project.ppath, addAll=addAll)[0]
+        if not success:
+            EricMessageBox.critical(
+                self.__ui,
+                self.tr("Create project repository"),
+                self.tr(
+                    """The project repository could not be created."""))
+        else:
+            pfn = project.pfile
+            if not os.path.isfile(pfn):
+                pfn += "z"
+            project.closeProject()
+            project.openProject(pfn)
+    
+    def vcsImport(self, vcsDataDict, projectDir, noDialog=False, addAll=True):
+        """
+        Public method used to import the project into the Mercurial repository.
+        
+        @param vcsDataDict dictionary of data required for the import
+        @type dict
+        @param projectDir project directory (string)
+        @type str
+        @param noDialog flag indicating quiet operations
+        @type bool
+        @param addAll flag indicating to add all files to the repository
+        @type bool
+        @return tuple containing a flag indicating an execution without errors
+            and a flag indicating the version controll status
+        @rtype tuple of (bool, bool)
+        """
+        msg = vcsDataDict["message"]
+        if not msg:
+            msg = '***'
+        
+        args = self.initCommand("init")
+        args.append(projectDir)
+        dia = HgDialog(self.tr('Creating Mercurial repository'), self)
+        res = dia.startProcess(args)
+        if res:
+            dia.exec()
+        status = dia.normalExit()
+        
+        if status:
+            self.stopClient()
+            self.__repoDir = projectDir
+            
+            ignoreName = os.path.join(projectDir, Hg.IgnoreFileName)
+            if not os.path.exists(ignoreName):
+                status = self.hgCreateIgnoreFile(projectDir)
+            
+            if status and addAll:
+                args = self.initCommand("commit")
+                args.append('--addremove')
+                args.append('--message')
+                args.append(msg)
+                dia = HgDialog(
+                    self.tr('Initial commit to Mercurial repository'),
+                    self)
+                res = dia.startProcess(args)
+                if res:
+                    dia.exec()
+                status = dia.normalExit()
+        
+        return status, False
+    
+    def vcsCheckout(self, vcsDataDict, projectDir, noDialog=False):
+        """
+        Public method used to check the project out of a Mercurial repository
+        (clone).
+        
+        @param vcsDataDict dictionary of data required for the checkout
+        @param projectDir project directory to create (string)
+        @param noDialog flag indicating quiet operations
+        @return flag indicating an execution without errors (boolean)
+        """
+        noDialog = False
+        try:
+            rev = vcsDataDict["revision"]
+        except KeyError:
+            rev = None
+        vcsUrl = self.hgNormalizeURL(vcsDataDict["url"])
+        
+        args = self.initCommand("clone")
+        if rev:
+            args.append("--rev")
+            args.append(rev)
+        if vcsDataDict["largefiles"]:
+            args.append("--all-largefiles")
+        args.append(vcsUrl)
+        args.append(projectDir)
+        
+        if noDialog:
+            out, err = self.__client.runcommand(args)
+            return err == ""
+        else:
+            dia = HgDialog(
+                self.tr('Cloning project from a Mercurial repository'),
+                self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+            return dia.normalExit()
+    
+    def vcsExport(self, vcsDataDict, projectDir):
+        """
+        Public method used to export a directory from the Mercurial repository.
+        
+        @param vcsDataDict dictionary of data required for the checkout
+        @param projectDir project directory to create (string)
+        @return flag indicating an execution without errors (boolean)
+        """
+        status = self.vcsCheckout(vcsDataDict, projectDir)
+        shutil.rmtree(os.path.join(projectDir, self.adminDir), True)
+        if os.path.exists(os.path.join(projectDir, Hg.IgnoreFileName)):
+            os.remove(os.path.join(projectDir, Hg.IgnoreFileName))
+        return status
+    
+    def vcsCommit(self, name, message, noDialog=False, closeBranch=False,
+                  mq=False, merge=False):
+        """
+        Public method used to make the change of a file/directory permanent
+        in the Mercurial repository.
+        
+        @param name file/directory name to be committed (string or list of
+            strings)
+        @param message message for this operation (string)
+        @param noDialog flag indicating quiet operations
+        @param closeBranch flag indicating a close branch commit (boolean)
+        @param mq flag indicating a queue commit (boolean)
+        @param merge flag indicating a merge commit (boolean)
+        """
+        msg = message
+        
+        if mq or merge:
+            # ensure dialog is shown for a queue commit
+            noDialog = False
+        
+        if not noDialog:
+            # call CommitDialog and get message from there
+            if self.__commitDialog is None:
+                from .HgCommitDialog import HgCommitDialog
+                self.__commitDialog = HgCommitDialog(self, msg, mq, merge,
+                                                     self.__ui)
+                self.__commitDialog.accepted.connect(self.__vcsCommit_Step2)
+            self.__commitDialog.show()
+            self.__commitDialog.raise_()
+            self.__commitDialog.activateWindow()
+        
+        self.__commitData["name"] = name
+        self.__commitData["msg"] = msg
+        self.__commitData["noDialog"] = noDialog
+        self.__commitData["closeBranch"] = closeBranch
+        self.__commitData["mq"] = mq
+        self.__commitData["merge"] = merge
+        
+        if noDialog:
+            self.__vcsCommit_Step2()
+    
+    def __vcsCommit_Step2(self):
+        """
+        Private slot performing the second step of the commit action.
+        """
+        name = self.__commitData["name"]
+        msg = self.__commitData["msg"]
+        noDialog = self.__commitData["noDialog"]
+        closeBranch = self.__commitData["closeBranch"]
+        mq = self.__commitData["mq"]
+        merge = self.__commitData["merge"]
+        
+        if not noDialog:
+            # check, if there are unsaved changes, that should be committed
+            if isinstance(name, list):
+                nameList = name
+            else:
+                nameList = [name]
+            ok = True
+            for nam in nameList:
+                # check for commit of the project
+                if os.path.isdir(nam):
+                    project = ericApp().getObject("Project")
+                    if nam == project.getProjectPath():
+                        ok &= (
+                            project.checkAllScriptsDirty(
+                                reportSyntaxErrors=True) and
+                            project.checkDirty()
+                        )
+                        continue
+                elif os.path.isfile(nam):
+                    editor = (
+                        ericApp().getObject("ViewManager").getOpenEditor(nam)
+                    )
+                    if editor:
+                        ok &= editor.checkDirty()
+                if not ok:
+                    break
+            
+            if not ok:
+                res = EricMessageBox.yesNo(
+                    self.__ui,
+                    self.tr("Commit Changes"),
+                    self.tr(
+                        """The commit affects files, that have unsaved"""
+                        """ changes. Shall the commit be continued?"""),
+                    icon=EricMessageBox.Warning)
+                if not res:
+                    return
+        
+        if self.__commitDialog is not None:
+            (msg, amend, commitSubrepositories, author,
+             dateTime) = self.__commitDialog.getCommitData()
+            self.__commitDialog.deleteLater()
+            self.__commitDialog = None
+            if amend and not msg:
+                msg = self.__getMostRecentCommitMessage()
+        else:
+            amend = False
+            commitSubrepositories = False
+            author = ""
+            dateTime = ""
+        
+        if not msg and not amend:
+            msg = '***'
+        
+        args = self.initCommand("commit")
+        args.append("-v")
+        if mq:
+            args.append("--mq")
+        elif merge:
+            if author:
+                args.append("--user")
+                args.append(author)
+            if dateTime:
+                args.append("--date")
+                args.append(dateTime)
+        else:
+            if closeBranch:
+                args.append("--close-branch")
+            if amend:
+                args.append("--amend")
+            if commitSubrepositories:
+                args.append("--subrepos")
+            if author:
+                args.append("--user")
+                args.append(author)
+            if dateTime:
+                args.append("--date")
+                args.append(dateTime)
+        if msg:
+            args.append("--message")
+            args.append(msg)
+        if isinstance(name, list):
+            self.addArguments(args, name)
+        else:
+            args.append(name)
+        
+        dia = HgDialog(
+            self.tr('Committing changes to Mercurial repository'),
+            self)
+        res = dia.startProcess(args)
+        if res:
+            dia.exec()
+        self.committed.emit()
+        if self.__forgotNames:
+            model = ericApp().getObject("Project").getModel()
+            for name in self.__forgotNames:
+                model.updateVCSStatus(name)
+            self.__forgotNames = []
+        self.checkVCSStatus()
+    
+    def vcsCommitMessages(self):
+        """
+        Public method to get the list of saved commit messages.
+        
+        @return list of saved commit messages
+        @rtype list of str
+        """
+        # try per project commit history first
+        messages = self._vcsProjectCommitMessages()
+        if not messages:
+            # empty list returned, try the vcs specific one
+            messages = self.getPlugin().getPreferences('Commits')
+        
+        return messages
+    
+    def vcsAddCommitMessage(self, message):
+        """
+        Public method to add a commit message to the list of saved messages.
+        
+        @param message message to be added
+        @type str
+        """
+        if not self._vcsAddProjectCommitMessage(message):
+            commitMessages = self.vcsCommitMessages()
+            if message in commitMessages:
+                commitMessages.remove(message)
+            commitMessages.insert(0, message)
+            no = Preferences.getVCS("CommitMessages")
+            del commitMessages[no:]
+            self.getPlugin().setPreferences('Commits', commitMessages)
+    
+    def vcsClearCommitMessages(self):
+        """
+        Public method to clear the list of saved messages.
+        """
+        if not self._vcsClearProjectCommitMessages():
+            self.getPlugin().setPreferences('Commits', [])
+    
+    def __getMostRecentCommitMessage(self):
+        """
+        Private method to get the most recent commit message.
+        
+        Note: This message is extracted from the parent commit of the
+        working directory.
+        
+        @return most recent commit message
+        @rtype str
+        """
+        args = self.initCommand("log")
+        args.append("--rev")
+        args.append(".")
+        args.append('--template')
+        args.append('{desc}')
+        
+        output, error = self.__client.runcommand(args)
+        
+        return output
+    
+    def vcsUpdate(self, name=None, noDialog=False, revision=None):
+        """
+        Public method used to update a file/directory with the Mercurial
+        repository.
+        
+        @param name file/directory name to be updated (not used)
+        @param noDialog flag indicating quiet operations (boolean)
+        @param revision revision to update to (string)
+        @return flag indicating, that the update contained an add
+            or delete (boolean)
+        """
+        args = self.initCommand("update")
+        if "-v" not in args and "--verbose" not in args:
+            args.append("-v")
+        if revision:
+            args.append("-r")
+            args.append(revision)
+        
+        if noDialog:
+            out, err = self.__client.runcommand(args)
+            res = False
+        else:
+            dia = HgDialog(self.tr(
+                'Synchronizing with the Mercurial repository'),
+                self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+                res = dia.hasAddOrDelete()
+        self.checkVCSStatus()
+        return res
+    
+    def vcsAdd(self, name, isDir=False, noDialog=False):
+        """
+        Public method used to add a file/directory to the Mercurial repository.
+        
+        @param name file/directory name to be added (string)
+        @param isDir flag indicating name is a directory (boolean)
+        @param noDialog flag indicating quiet operations
+        """
+        args = self.initCommand("add")
+        args.append("-v")
+        
+        if isinstance(name, list):
+            self.addArguments(args, name)
+        else:
+            args.append(name)
+        
+        if noDialog:
+            out, err = self.__client.runcommand(args)
+        else:
+            dia = HgDialog(
+                self.tr(
+                    'Adding files/directories to the Mercurial repository'),
+                self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+    
+    def vcsAddBinary(self, name, isDir=False):
+        """
+        Public method used to add a file/directory in binary mode to the
+        Mercurial repository.
+        
+        @param name file/directory name to be added (string)
+        @param isDir flag indicating name is a directory (boolean)
+        """
+        self.vcsAdd(name, isDir)
+    
+    def vcsAddTree(self, path):
+        """
+        Public method to add a directory tree rooted at path to the Mercurial
+        repository.
+        
+        @param path root directory of the tree to be added (string or list of
+            strings))
+        """
+        self.vcsAdd(path, isDir=False)
+    
+    def vcsRemove(self, name, project=False, noDialog=False):
+        """
+        Public method used to remove a file/directory from the Mercurial
+        repository.
+        
+        The default operation is to remove the local copy as well.
+        
+        @param name file/directory name to be removed (string or list of
+            strings))
+        @param project flag indicating deletion of a project tree (boolean)
+            (not needed)
+        @param noDialog flag indicating quiet operations
+        @return flag indicating successfull operation (boolean)
+        """
+        args = self.initCommand("remove")
+        args.append("-v")
+        if noDialog and '--force' not in args:
+            args.append('--force')
+        
+        if isinstance(name, list):
+            self.addArguments(args, name)
+        else:
+            args.append(name)
+        
+        if noDialog:
+            out, err = self.__client.runcommand(args)
+            res = err == ""
+        else:
+            dia = HgDialog(
+                self.tr(
+                    'Removing files/directories from the Mercurial'
+                    ' repository'),
+                self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+                res = dia.normalExitWithoutErrors()
+        
+        return res
+    
+    def vcsMove(self, name, project, target=None, noDialog=False):
+        """
+        Public method used to move a file/directory.
+        
+        @param name file/directory name to be moved (string)
+        @param project reference to the project object
+        @param target new name of the file/directory (string)
+        @param noDialog flag indicating quiet operations
+        @return flag indicating successfull operation (boolean)
+        """
+        isDir = os.path.isdir(name)
+        
+        res = False
+        if noDialog:
+            if target is None:
+                return False
+            force = True
+            accepted = True
+        else:
+            from .HgCopyDialog import HgCopyDialog
+            dlg = HgCopyDialog(name, None, True)
+            accepted = dlg.exec() == QDialog.DialogCode.Accepted
+            if accepted:
+                target, force = dlg.getData()
+        
+        if accepted:
+            args = self.initCommand("rename")
+            args.append("-v")
+            if force:
+                args.append('--force')
+            args.append(name)
+            args.append(target)
+            
+            if noDialog:
+                out, err = self.__client.runcommand(args)
+                res = err == ""
+            else:
+                dia = HgDialog(self.tr('Renaming {0}').format(name), self)
+                res = dia.startProcess(args)
+                if res:
+                    dia.exec()
+                    res = dia.normalExit()
+            if res:
+                if target.startswith(project.getProjectPath()):
+                    if isDir:
+                        project.moveDirectory(name, target)
+                    else:
+                        project.renameFileInPdata(name, target)
+                else:
+                    if isDir:
+                        project.removeDirectory(name)
+                    else:
+                        project.removeFile(name)
+        return res
+    
+    def vcsDiff(self, name):
+        """
+        Public method used to view the difference of a file/directory to the
+        Mercurial repository.
+        
+        If name is a directory and is the project directory, all project files
+        are saved first. If name is a file (or list of files), which is/are
+        being edited and has unsaved modification, they can be saved or the
+        operation may be aborted.
+        
+        @param name file/directory name to be diffed (string)
+        """
+        names = name[:] if isinstance(name, list) else [name]
+        for nam in names:
+            if os.path.isfile(nam):
+                editor = ericApp().getObject("ViewManager").getOpenEditor(nam)
+                if editor and not editor.checkDirty():
+                    return
+            else:
+                project = ericApp().getObject("Project")
+                if nam == project.ppath and not project.saveAllScripts():
+                    return
+        if self.diff is None:
+            from .HgDiffDialog import HgDiffDialog
+            self.diff = HgDiffDialog(self)
+        self.diff.show()
+        self.diff.raise_()
+        QApplication.processEvents()
+        self.diff.start(name, refreshable=True)
+    
+    def vcsStatus(self, name):
+        """
+        Public method used to view the status of files/directories in the
+        Mercurial repository.
+        
+        @param name file/directory name(s) to show the status of
+            (string or list of strings)
+        """
+        if self.status is None:
+            from .HgStatusDialog import HgStatusDialog
+            self.status = HgStatusDialog(self)
+        self.status.show()
+        self.status.raise_()
+        self.status.start(name)
+    
+    def hgSummary(self, mq=False, largefiles=False):
+        """
+        Public method used to show some summary information of the
+        working directory state.
+        
+        @param mq flag indicating to show the queue status as well (boolean)
+        @param largefiles flag indicating to show the largefiles status as
+            well (boolean)
+        """
+        if self.summary is None:
+            from .HgSummaryDialog import HgSummaryDialog
+            self.summary = HgSummaryDialog(self)
+        self.summary.show()
+        self.summary.raise_()
+        self.summary.start(mq=mq, largefiles=largefiles)
+    
+    def vcsTag(self, name=None, revision=None, tagName=None):
+        """
+        Public method used to set/remove a tag in the Mercurial repository.
+        
+        @param name file/directory name to determine the repo root from
+            (string)
+        @param revision revision to set tag for (string)
+        @param tagName name of the tag (string)
+        @return flag indicating a performed tag action (boolean)
+        """
+        from .HgTagDialog import HgTagDialog
+        dlg = HgTagDialog(self.hgGetTagsList(withType=True),
+                          revision, tagName)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            tag, revision, tagOp, force = dlg.getParameters()
+        else:
+            return False
+        
+        args = self.initCommand("tag")
+        msgPart = ""
+        if tagOp in [HgTagDialog.CreateLocalTag, HgTagDialog.DeleteLocalTag]:
+            args.append('--local')
+            msgPart = "local "
+        else:
+            msgPart = "global "
+        if tagOp in [HgTagDialog.DeleteGlobalTag, HgTagDialog.DeleteLocalTag]:
+            args.append('--remove')
+        if (
+            tagOp in [
+                HgTagDialog.CreateGlobalTag, HgTagDialog.CreateLocalTag] and
+            revision
+        ):
+            args.append("--rev")
+            args.append(revision)
+        if force:
+            args.append("--force")
+        args.append('--message')
+        if tagOp in [HgTagDialog.CreateGlobalTag, HgTagDialog.CreateLocalTag]:
+            tag = tag.strip().replace(" ", "_")
+            args.append("Created {1}tag <{0}>.".format(tag, msgPart))
+        else:
+            args.append("Removed {1}tag <{0}>.".format(tag, msgPart))
+        args.append(tag)
+        
+        dia = HgDialog(self.tr('Tagging in the Mercurial repository'),
+                       self)
+        res = dia.startProcess(args)
+        if res:
+            dia.exec()
+        
+        return True
+    
+    def vcsRevert(self, name):
+        """
+        Public method used to revert changes made to a file/directory.
+        
+        @param name file/directory name to be reverted
+        @type str
+        @return flag indicating, that the update contained an add
+            or delete
+        @rtype bool
+        """
+        args = self.initCommand("revert")
+        if not self.getPlugin().getPreferences("CreateBackup"):
+            args.append("--no-backup")
+        args.append("-v")
+        if isinstance(name, list):
+            self.addArguments(args, name)
+            names = name[:]
+        else:
+            args.append(name)
+            names = [name]
+        
+        project = ericApp().getObject("Project")
+        names = [project.getRelativePath(nam) for nam in names]
+        if names[0]:
+            from UI.DeleteFilesConfirmationDialog import (
+                DeleteFilesConfirmationDialog
+            )
+            dlg = DeleteFilesConfirmationDialog(
+                self.parent(),
+                self.tr("Revert changes"),
+                self.tr(
+                    "Do you really want to revert all changes to these files"
+                    " or directories?"),
+                names)
+            yes = dlg.exec() == QDialog.DialogCode.Accepted
+        else:
+            yes = EricMessageBox.yesNo(
+                None,
+                self.tr("Revert changes"),
+                self.tr("""Do you really want to revert all changes of"""
+                        """ the project?"""))
+        if yes:
+            dia = HgDialog(self.tr('Reverting changes'), self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+                res = dia.hasAddOrDelete()
+            self.checkVCSStatus()
+        else:
+            res = False
+        
+        return res
+    
+    def vcsMerge(self, name, rev=""):
+        """
+        Public method used to merge a URL/revision into the local project.
+        
+        @param name file/directory name to be merged
+        @type str
+        @param rev revision to merge with
+        @type str
+        """
+        if not rev:
+            from .HgMergeDialog import HgMergeDialog
+            dlg = HgMergeDialog(self.hgGetTagsList(),
+                                self.hgGetBranchesList(),
+                                self.hgGetBookmarksList())
+            if dlg.exec() == QDialog.DialogCode.Accepted:
+                rev, force = dlg.getParameters()
+            else:
+                return
+        else:
+            force = False
+        
+        args = self.initCommand("merge")
+        if force:
+            args.append("--force")
+        if self.getPlugin().getPreferences("InternalMerge"):
+            args.append("--tool")
+            args.append("internal:merge")
+        if rev:
+            args.append("--rev")
+            args.append(rev)
+        
+        dia = HgDialog(self.tr('Merging'), self)
+        res = dia.startProcess(args)
+        if res:
+            dia.exec()
+        self.checkVCSStatus()
+    
+    def hgReMerge(self, name):
+        """
+        Public method used to merge a URL/revision into the local project.
+        
+        @param name file/directory name to be merged (string)
+        """
+        args = self.initCommand("resolve")
+        if self.getPlugin().getPreferences("InternalMerge"):
+            args.append("--tool")
+            args.append("internal:merge")
+        if isinstance(name, list):
+            self.addArguments(args, name)
+            names = name[:]
+        else:
+            args.append(name)
+            names = [name]
+        
+        project = ericApp().getObject("Project")
+        names = [project.getRelativePath(nam) for nam in names]
+        if names[0]:
+            from UI.DeleteFilesConfirmationDialog import (
+                DeleteFilesConfirmationDialog
+            )
+            dlg = DeleteFilesConfirmationDialog(
+                self.parent(),
+                self.tr("Re-Merge"),
+                self.tr(
+                    "Do you really want to re-merge these files"
+                    " or directories?"),
+                names)
+            yes = dlg.exec() == QDialog.DialogCode.Accepted
+        else:
+            yes = EricMessageBox.yesNo(
+                None,
+                self.tr("Re-Merge"),
+                self.tr("""Do you really want to re-merge the project?"""))
+        if yes:
+            dia = HgDialog(self.tr('Re-Merging').format(name), self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+            self.checkVCSStatus()
+    
+    def vcsSwitch(self, name):
+        """
+        Public method used to switch a working directory to a different
+        revision.
+        
+        @param name directory name to be switched (string)
+        @return flag indicating, that the switch contained an add
+            or delete (boolean)
+        """
+        from .HgRevisionSelectionDialog import HgRevisionSelectionDialog
+        dlg = HgRevisionSelectionDialog(
+            self.hgGetTagsList(),
+            self.hgGetBranchesList(),
+            bookmarksList=self.hgGetBookmarksList(),
+            noneLabel=self.tr("Current branch tip")
+        )
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            rev = dlg.getRevision()
+            return self.vcsUpdate(name, revision=rev)
+        
+        return False
+
+    def vcsRegisteredState(self, name):
+        """
+        Public method used to get the registered state of a file in the vcs.
+        
+        @param name file or directory name to check
+        @type str
+        @return a combination of canBeCommited and canBeAdded
+        @rtype int
+        """
+        if name.endswith(os.sep):
+            name = name[:-1]
+        name = os.path.normcase(name)
+        
+        if (
+            os.path.isdir(name) and
+            os.path.isdir(os.path.join(name, self.adminDir))
+        ):
+            return self.canBeCommitted
+        
+        if name in self.statusCache:
+            return self.statusCache[name]
+        args = self.initCommand("status")
+        args.append('--all')
+        args.append('--noninteractive')
+        
+        output, error = self.__client.runcommand(args)
+        
+        if output:
+            repodir = self.getClient().getRepository()
+            for line in output.splitlines():
+                if len(line) > 2 and line[0] in "MARC!?I" and line[1] == " ":
+                    flag, path = line.split(" ", 1)
+                    absname = Utilities.normcasepath(
+                        os.path.join(repodir, path))
+                    if flag not in "?I" and absname == name:
+                        return self.canBeCommitted
+        
+        return self.canBeAdded
+    
+    def vcsAllRegisteredStates(self, names, dname, shortcut=True):
+        """
+        Public method used to get the registered states of a number of files
+        in the vcs.
+        
+        <b>Note:</b> If a shortcut is to be taken, the code will only check,
+        if the named directory has been scanned already. If so, it is assumed,
+        that the states for all files have been populated by the previous run.
+        
+        @param names dictionary with all filenames to be checked as keys
+        @param dname directory to check in (string)
+        @param shortcut flag indicating a shortcut should be taken (boolean)
+        @return the received dictionary completed with a combination of
+            canBeCommited and canBeAdded or None in order to signal an error
+        """
+        if dname.endswith(os.sep):
+            dname = dname[:-1]
+        dname = os.path.normcase(dname)
+        
+        found = False
+        for name in list(self.statusCache.keys()):
+            if name in names:
+                found = True
+                names[name] = self.statusCache[name]
+        
+        if not found:
+            args = self.initCommand("status")
+            args.append('--all')
+            args.append('--noninteractive')
+            
+            output, error = self.__client.runcommand(args)
+            
+            if output:
+                repoPath = self.getClient().getRepository()
+                dirs = [x for x in names.keys() if os.path.isdir(x)]
+                for line in output.splitlines():
+                    if line and line[0] in "MARC!?I":
+                        flag, path = line.split(" ", 1)
+                        name = os.path.normcase(os.path.join(repoPath, path))
+                        dirName = os.path.dirname(name)
+                        if name.startswith(dname) and flag not in "?I":
+                            if name in names:
+                                names[name] = self.canBeCommitted
+                            if dirName in names:
+                                names[dirName] = self.canBeCommitted
+                            if dirs:
+                                for d in dirs:
+                                    if name.startswith(d):
+                                        names[d] = self.canBeCommitted
+                                        dirs.remove(d)
+                                        break
+                        if flag not in "?I":
+                            self.statusCache[name] = self.canBeCommitted
+                            self.statusCache[dirName] = self.canBeCommitted
+                        else:
+                            self.statusCache[name] = self.canBeAdded
+                            if dirName not in self.statusCache:
+                                self.statusCache[dirName] = self.canBeAdded
+        
+        return names
+    
+    def clearStatusCache(self):
+        """
+        Public method to clear the status cache.
+        """
+        self.statusCache = {}
+    
+    def vcsName(self):
+        """
+        Public method returning the name of the vcs.
+        
+        @return always 'Mercurial' (string)
+        """
+        return "Mercurial"
+    
+    def vcsInitConfig(self, project):
+        """
+        Public method to initialize the VCS configuration.
+        
+        This method ensures, that an ignore file exists.
+        
+        @param project reference to the project (Project)
+        """
+        ppath = project.getProjectPath()
+        if ppath:
+            ignoreName = os.path.join(ppath, Hg.IgnoreFileName)
+            if not os.path.exists(ignoreName):
+                self.hgCreateIgnoreFile(project.getProjectPath(), autoAdd=True)
+    
+    def vcsCleanup(self, name):
+        """
+        Public method used to cleanup the working directory.
+        
+        @param name directory name to be cleaned up (string)
+        """
+        patterns = self.getPlugin().getPreferences("CleanupPatterns").split()
+        
+        entries = []
+        for pat in patterns:
+            entries.extend(Utilities.direntries(name, True, pat))
+        
+        for entry in entries:
+            with contextlib.suppress(OSError):
+                os.remove(entry)
+    
+    def vcsCommandLine(self, name):
+        """
+        Public method used to execute arbitrary mercurial commands.
+        
+        @param name directory name of the working directory (string)
+        """
+        from .HgCommandDialog import HgCommandDialog
+        dlg = HgCommandDialog(self.commandHistory, name)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            command = dlg.getData()
+            commandList = Utilities.parseOptionString(command)
+            
+            # This moves any previous occurrence of these arguments to the head
+            # of the list.
+            if command in self.commandHistory:
+                self.commandHistory.remove(command)
+            self.commandHistory.insert(0, command)
+            
+            args = []
+            self.addArguments(args, commandList)
+            
+            dia = HgDialog(self.tr('Mercurial command'), self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+    
+    def vcsOptionsDialog(self, project, archive, editable=False, parent=None):
+        """
+        Public method to get a dialog to enter repository info.
+        
+        @param project reference to the project object
+        @param archive name of the project in the repository (string)
+        @param editable flag indicating that the project name is editable
+            (boolean)
+        @param parent parent widget (QWidget)
+        @return reference to the instantiated options dialog (HgOptionsDialog)
+        """
+        from .HgOptionsDialog import HgOptionsDialog
+        return HgOptionsDialog(self, project, parent)
+    
+    def vcsNewProjectOptionsDialog(self, parent=None):
+        """
+        Public method to get a dialog to enter repository info for getting a
+        new project.
+        
+        @param parent parent widget (QWidget)
+        @return reference to the instantiated options dialog
+            (HgNewProjectOptionsDialog)
+        """
+        from .HgNewProjectOptionsDialog import HgNewProjectOptionsDialog
+        return HgNewProjectOptionsDialog(self, parent)
+    
+    def vcsRepositoryInfos(self, ppath):
+        """
+        Public method to retrieve information about the repository.
+        
+        @param ppath local path to get the repository infos (string)
+        @return string with ready formated info for display (string)
+        """
+        args = self.initCommand("parents")
+        args.append('--template')
+        args.append('{rev}:{node|short}@@@{tags}@@@{author|xmlescape}@@@'
+                    '{date|isodate}@@@{branches}@@@{bookmarks}\n')
+        
+        output, error = self.__client.runcommand(args)
+        
+        infoBlock = []
+        if output:
+            for index, line in enumerate(output.splitlines(), start=1):
+                (changeset, tags, author, date, branches,
+                 bookmarks) = line.split("@@@")
+                cdate, ctime = date.split()[:2]
+                info = []
+                info.append(QCoreApplication.translate(
+                    "mercurial",
+                    """<tr><td><b>Parent #{0}</b></td><td></td></tr>\n"""
+                    """<tr><td><b>Changeset</b></td><td>{1}</td></tr>""")
+                    .format(index, changeset))
+                if tags:
+                    info.append(QCoreApplication.translate(
+                        "mercurial",
+                        """<tr><td><b>Tags</b></td><td>{0}</td></tr>""")
+                        .format('<br/>'.join(tags.split())))
+                if bookmarks:
+                    info.append(QCoreApplication.translate(
+                        "mercurial",
+                        """<tr><td><b>Bookmarks</b></td><td>{0}</td></tr>""")
+                        .format('<br/>'.join(bookmarks.split())))
+                if branches:
+                    info.append(QCoreApplication.translate(
+                        "mercurial",
+                        """<tr><td><b>Branches</b></td><td>{0}</td></tr>""")
+                        .format('<br/>'.join(branches.split())))
+                info.append(QCoreApplication.translate(
+                    "mercurial",
+                    """<tr><td><b>Last author</b></td><td>{0}</td></tr>\n"""
+                    """<tr><td><b>Committed date</b></td><td>{1}</td></tr>\n"""
+                    """<tr><td><b>Committed time</b></td><td>{2}</td></tr>""")
+                    .format(author, cdate, ctime))
+                infoBlock.append("\n".join(info))
+        infoStr = (
+            """<tr></tr>{0}""".format("<tr></tr>".join(infoBlock))
+            if infoBlock else
+            ""
+        )
+        
+        url = ""
+        args = self.initCommand("showconfig")
+        args.append('paths.default')
+        
+        output, error = self.__client.runcommand(args)
+        url = output.splitlines()[0].strip() if output else ""
+        
+        return QCoreApplication.translate(
+            'mercurial',
+            """<h3>Repository information</h3>\n"""
+            """<p><table>\n"""
+            """<tr><td><b>Mercurial V.</b></td><td>{0}</td></tr>\n"""
+            """<tr></tr>\n"""
+            """<tr><td><b>URL</b></td><td>{1}</td></tr>\n"""
+            """{2}"""
+            """</table></p>\n"""
+        ).format(self.versionStr, url, infoStr)
+    
+    def vcsSupportCommandOptions(self):
+        """
+        Public method to signal the support of user settable command options.
+        
+        @return flag indicating the support  of user settable command options
+            (boolean)
+        """
+        return False
+    
+    ###########################################################################
+    ## Private Mercurial specific methods are below.
+    ###########################################################################
+    
+    def hgNormalizeURL(self, url):
+        """
+        Public method to normalize a url for Mercurial.
+        
+        @param url url string (string)
+        @return properly normalized url for mercurial (string)
+        """
+        url = url.replace('\\', '/')
+        if url.endswith('/'):
+            url = url[:-1]
+        urll = url.split('//')
+        return "{0}//{1}".format(urll[0], '/'.join(urll[1:]))
+    
+    def hgCopy(self, name, project):
+        """
+        Public method used to copy a file/directory.
+        
+        @param name file/directory name to be copied (string)
+        @param project reference to the project object
+        @return flag indicating successful operation (boolean)
+        """
+        from .HgCopyDialog import HgCopyDialog
+        dlg = HgCopyDialog(name)
+        res = False
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            target, force = dlg.getData()
+            
+            args = self.initCommand("copy")
+            args.append("-v")
+            args.append(name)
+            args.append(target)
+            
+            dia = HgDialog(
+                self.tr('Copying {0}').format(name), self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+                res = dia.normalExit()
+                if (
+                    res and
+                    target.startswith(project.getProjectPath())
+                ):
+                    if os.path.isdir(name):
+                        project.copyDirectory(name, target)
+                    else:
+                        project.appendFile(target)
+        return res
+    
+    def hgGetTagsList(self, withType=False):
+        """
+        Public method to get the list of tags.
+        
+        @param withType flag indicating to get the tag type as well (boolean)
+        @return list of tags (list of string) or list of tuples of
+            tag name and flag indicating a local tag (list of tuple of string
+            and boolean), if withType is True
+        """
+        args = self.initCommand("tags")
+        args.append('--verbose')
+        
+        output, error = self.__client.runcommand(args)
+        
+        tagsList = []
+        if output:
+            for line in output.splitlines():
+                li = line.strip().split()
+                if li[-1][0] in "1234567890":
+                    # last element is a rev:changeset
+                    del li[-1]
+                    isLocal = False
+                else:
+                    del li[-2:]
+                    isLocal = True
+                name = " ".join(li)
+                if name not in ["tip", "default"]:
+                    if withType:
+                        tagsList.append((name, isLocal))
+                    else:
+                        tagsList.append(name)
+        
+        if withType:
+            return tagsList
+        else:
+            if tagsList:
+                self.tagsList = tagsList
+            return self.tagsList[:]
+    
+    def hgGetBranchesList(self):
+        """
+        Public method to get the list of branches.
+        
+        @return list of branches (list of string)
+        """
+        args = self.initCommand("branches")
+        args.append('--closed')
+        
+        output, error = self.__client.runcommand(args)
+        
+        if output:
+            self.branchesList = []
+            for line in output.splitlines():
+                li = line.strip().split()
+                if li[-1][0] in "1234567890":
+                    # last element is a rev:changeset
+                    del li[-1]
+                else:
+                    del li[-2:]
+                name = " ".join(li)
+                if name not in ["tip", "default"]:
+                    self.branchesList.append(name)
+        
+        return self.branchesList[:]
+    
+    def hgListTagBranch(self, tags=True):
+        """
+        Public method used to list the available tags or branches.
+        
+        @param tags flag indicating listing of branches or tags
+                (False = branches, True = tags)
+        """
+        from .HgTagBranchListDialog import HgTagBranchListDialog
+        self.tagbranchList = HgTagBranchListDialog(self)
+        self.tagbranchList.show()
+        if tags:
+            if not self.showedTags:
+                self.showedTags = True
+                allTagsBranchesList = self.allTagsBranchesList
+            else:
+                self.tagsList = []
+                allTagsBranchesList = None
+            self.tagbranchList.start(
+                tags, self.tagsList, allTagsBranchesList)
+        else:
+            if not self.showedBranches:
+                self.showedBranches = True
+                allTagsBranchesList = self.allTagsBranchesList
+            else:
+                self.branchesList = []
+                allTagsBranchesList = None
+            self.tagbranchList.start(
+                tags, self.branchesList, self.allTagsBranchesList)
+    
+    def hgAnnotate(self, name):
+        """
+        Public method to show the output of the hg annotate command.
+        
+        @param name file name to show the annotations for (string)
+        """
+        if self.annotate is None:
+            from .HgAnnotateDialog import HgAnnotateDialog
+            self.annotate = HgAnnotateDialog(self)
+        self.annotate.show()
+        self.annotate.raise_()
+        self.annotate.start(name)
+    
+    def hgExtendedDiff(self, name):
+        """
+        Public method used to view the difference of a file/directory to the
+        Mercurial repository.
+        
+        If name is a directory and is the project directory, all project files
+        are saved first. If name is a file (or list of files), which is/are
+        being edited and has unsaved modification, they can be saved or the
+        operation may be aborted.
+        
+        This method gives the chance to enter the revisions to be compared.
+        
+        @param name file/directory name to be diffed (string)
+        """
+        names = name[:] if isinstance(name, list) else [name]
+        for nam in names:
+            if os.path.isfile(nam):
+                editor = ericApp().getObject("ViewManager").getOpenEditor(nam)
+                if editor and not editor.checkDirty():
+                    return
+            else:
+                project = ericApp().getObject("Project")
+                if nam == project.ppath and not project.saveAllScripts():
+                    return
+        
+        from .HgRevisionsSelectionDialog import HgRevisionsSelectionDialog
+        dlg = HgRevisionsSelectionDialog(
+            self.hgGetTagsList(),
+            self.hgGetBranchesList(),
+            bookmarksList=self.hgGetBookmarksList()
+        )
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            revisions = dlg.getRevisions()
+            if self.diff is None:
+                from .HgDiffDialog import HgDiffDialog
+                self.diff = HgDiffDialog(self)
+            self.diff.show()
+            self.diff.raise_()
+            self.diff.start(name, revisions)
+    
+    def __hgGetFileForRevision(self, name, rev=""):
+        """
+        Private method to get a file for a specific revision from the
+        repository.
+        
+        @param name file name to get from the repository (string)
+        @param rev revision to retrieve (string)
+        @return contents of the file (string) and an error message (string)
+        """
+        args = self.initCommand("cat")
+        if rev:
+            args.append("--rev")
+            args.append(rev)
+        args.append(name)
+        
+        output, error = self.__client.runcommand(args)
+        
+        # return file contents with 'universal newlines'
+        return output.replace('\r\n', '\n').replace('\r', '\n'), error
+    
+    def vcsSbsDiff(self, name, extended=False, revisions=None):
+        """
+        Public method used to view the difference of a file to the Mercurial
+        repository side-by-side.
+        
+        @param name file name to be diffed (string)
+        @param extended flag indicating the extended variant (boolean)
+        @param revisions tuple of two revisions (tuple of strings)
+        @exception ValueError raised to indicate an invalid name parameter
+        """
+        if isinstance(name, list):
+            raise ValueError("Wrong parameter type")
+        
+        if extended:
+            from .HgRevisionsSelectionDialog import HgRevisionsSelectionDialog
+            dlg = HgRevisionsSelectionDialog(
+                self.hgGetTagsList(),
+                self.hgGetBranchesList(),
+                bookmarksList=self.hgGetBookmarksList()
+            )
+            if dlg.exec() == QDialog.DialogCode.Accepted:
+                rev1, rev2 = dlg.getRevisions()
+            else:
+                return
+        elif revisions:
+            rev1, rev2 = revisions[0], revisions[1]
+        else:
+            rev1, rev2 = "", ""
+        
+        output1, error = self.__hgGetFileForRevision(name, rev=rev1)
+        if error:
+            EricMessageBox.critical(
+                self.__ui,
+                self.tr("Mercurial Side-by-Side Difference"),
+                error)
+            return
+        name1 = "{0} (rev. {1})".format(name, rev1 and rev1 or ".")
+        
+        if rev2:
+            output2, error = self.__hgGetFileForRevision(name, rev=rev2)
+            if error:
+                EricMessageBox.critical(
+                    self.__ui,
+                    self.tr("Mercurial Side-by-Side Difference"),
+                    error)
+                return
+            name2 = "{0} (rev. {1})".format(name, rev2)
+        else:
+            try:
+                with open(name, "r", encoding="utf-8") as f1:
+                    output2 = f1.read()
+                name2 = "{0} (Work)".format(name)
+            except OSError:
+                EricMessageBox.critical(
+                    self.__ui,
+                    self.tr("Mercurial Side-by-Side Difference"),
+                    self.tr(
+                        """<p>The file <b>{0}</b> could not be read.</p>""")
+                    .format(name))
+                return
+        
+        if self.sbsDiff is None:
+            from UI.CompareDialog import CompareDialog
+            self.sbsDiff = CompareDialog()
+        self.sbsDiff.show()
+        self.sbsDiff.raise_()
+        self.sbsDiff.compare(output1, output2, name1, name2)
+    
+    def vcsLogBrowser(self, name=None, isFile=False):
+        """
+        Public method used to browse the log of a file/directory from the
+        Mercurial repository.
+        
+        @param name file/directory name to show the log of (string)
+        @param isFile flag indicating log for a file is to be shown
+            (boolean)
+        """
+        if name == self.getClient().getRepository():
+            name = None
+        
+        if self.logBrowser is None:
+            from .HgLogBrowserDialog import HgLogBrowserDialog
+            self.logBrowser = HgLogBrowserDialog(self)
+        self.logBrowser.show()
+        self.logBrowser.raise_()
+        self.logBrowser.start(name=name, isFile=isFile)
+    
+    def hgIncoming(self):
+        """
+        Public method used to view the log of incoming changes from the
+        Mercurial repository.
+        """
+        if self.logBrowserIncoming is None:
+            from .HgLogBrowserDialog import HgLogBrowserDialog
+            self.logBrowserIncoming = HgLogBrowserDialog(
+                self, mode="incoming")
+        self.logBrowserIncoming.show()
+        self.logBrowserIncoming.raise_()
+        self.logBrowserIncoming.start()
+    
+    def hgOutgoing(self):
+        """
+        Public method used to view the log of outgoing changes from the
+        Mercurial repository.
+        """
+        if self.logBrowserOutgoing is None:
+            from .HgLogBrowserDialog import HgLogBrowserDialog
+            self.logBrowserOutgoing = HgLogBrowserDialog(
+                self, mode="outgoing")
+        self.logBrowserOutgoing.show()
+        self.logBrowserOutgoing.raise_()
+        self.logBrowserOutgoing.start()
+    
+    def hgPull(self, revisions=None):
+        """
+        Public method used to pull changes from a remote Mercurial repository.
+        
+        @param revisions list of revisions to be pulled
+        @type list of str
+        @return flag indicating, that the update contained an add
+            or delete
+        @rtype bool
+        """
+        if (
+            self.getPlugin().getPreferences("PreferUnbundle") and
+            self.bundleFile and
+            os.path.exists(self.bundleFile) and
+            revisions is None
+        ):
+            command = "unbundle"
+            title = self.tr('Apply changegroups')
+        else:
+            command = "pull"
+            title = self.tr('Pulling from a remote Mercurial repository')
+        
+        args = self.initCommand(command)
+        args.append('-v')
+        if self.getPlugin().getPreferences("PullUpdate"):
+            args.append('--update')
+        if command == "unbundle":
+            args.append(self.bundleFile)
+        if revisions:
+            for rev in revisions:
+                args.append("--rev")
+                args.append(rev)
+        
+        dia = HgDialog(title, self)
+        res = dia.startProcess(args)
+        if res:
+            dia.exec()
+            res = dia.hasAddOrDelete()
+        if (
+            self.bundleFile and
+            os.path.exists(self.bundleFile)
+        ):
+            os.remove(self.bundleFile)
+            self.bundleFile = None
+        self.checkVCSStatus()
+        return res
+    
+    def hgPush(self, force=False, newBranch=False, rev=None):
+        """
+        Public method used to push changes to a remote Mercurial repository.
+        
+        @param force flag indicating a forced push (boolean)
+        @param newBranch flag indicating to push a new branch (boolean)
+        @param rev revision to be pushed (including all ancestors) (string)
+        """
+        args = self.initCommand("push")
+        args.append('-v')
+        if force:
+            args.append('-f')
+        if newBranch:
+            args.append('--new-branch')
+        if rev:
+            args.append('--rev')
+            args.append(rev)
+        
+        dia = HgDialog(
+            self.tr('Pushing to a remote Mercurial repository'), self)
+        res = dia.startProcess(args)
+        if res:
+            dia.exec()
+        self.checkVCSStatus()
+    
+    def hgInfo(self, mode="heads"):
+        """
+        Public method to show information about the heads of the repository.
+        
+        @param mode mode of the operation (string, one of heads, parents,
+            tip)
+        """
+        if mode not in ("heads", "parents", "tip"):
+            mode = "heads"
+        
+        info = []
+        
+        args = self.initCommand(mode)
+        args.append('--template')
+        args.append('{rev}:{node|short}@@@{tags}@@@{author|xmlescape}@@@'
+                    '{date|isodate}@@@{branches}@@@{parents}@@@{bookmarks}\n')
+        
+        output, error = self.__client.runcommand(args)
+        
+        if output:
+            for index, line in enumerate(output.splitlines(), start=1):
+                (changeset, tags, author, date, branches, parents,
+                 bookmarks) = line.split("@@@")
+                cdate, ctime = date.split()[:2]
+                info.append("""<p><table>""")
+                if mode == "heads":
+                    info.append(QCoreApplication.translate(
+                        "mercurial",
+                        """<tr><td><b>Head #{0}</b></td><td></td></tr>\n""")
+                        .format(index))
+                elif mode == "parents":
+                    info.append(QCoreApplication.translate(
+                        "mercurial",
+                        """<tr><td><b>Parent #{0}</b></td><td></td></tr>\n""")
+                        .format(index))
+                elif mode == "tip":
+                    info.append(QCoreApplication.translate(
+                        "mercurial",
+                        """<tr><td><b>Tip</b></td><td></td></tr>\n"""))
+                info.append(QCoreApplication.translate(
+                    "mercurial",
+                    """<tr><td><b>Changeset</b></td><td>{0}</td></tr>""")
+                    .format(changeset))
+                if tags:
+                    info.append(QCoreApplication.translate(
+                        "mercurial",
+                        """<tr><td><b>Tags</b></td><td>{0}</td></tr>""")
+                        .format('<br/>'.join(tags.split())))
+                if bookmarks:
+                    info.append(QCoreApplication.translate(
+                        "mercurial",
+                        """<tr><td><b>Bookmarks</b></td><td>{0}</td></tr>""")
+                        .format('<br/>'.join(bookmarks.split())))
+                if branches:
+                    info.append(QCoreApplication.translate(
+                        "mercurial",
+                        """<tr><td><b>Branches</b></td><td>{0}</td></tr>""")
+                        .format('<br/>'.join(branches.split())))
+                if parents:
+                    info.append(QCoreApplication.translate(
+                        "mercurial",
+                        """<tr><td><b>Parents</b></td><td>{0}</td></tr>""")
+                        .format('<br/>'.join(parents.split())))
+                info.append(QCoreApplication.translate(
+                    "mercurial",
+                    """<tr><td><b>Last author</b></td><td>{0}</td></tr>\n"""
+                    """<tr><td><b>Committed date</b></td><td>{1}</td></tr>\n"""
+                    """<tr><td><b>Committed time</b></td><td>{2}</td></tr>\n"""
+                    """</table></p>""")
+                    .format(author, cdate, ctime))
+            
+            dlg = VcsRepositoryInfoDialog(None, "\n".join(info))
+            dlg.exec()
+    
+    def hgConflicts(self):
+        """
+        Public method used to show a list of files containing conflicts.
+        """
+        if self.conflictsDlg is None:
+            from .HgConflictsListDialog import HgConflictsListDialog
+            self.conflictsDlg = HgConflictsListDialog(self)
+        self.conflictsDlg.show()
+        self.conflictsDlg.raise_()
+        self.conflictsDlg.start()
+    
+    def vcsResolved(self, name, unresolve=False):
+        """
+        Public method used to resolve conflicts of a file/directory.
+        
+        @param name file/directory name to be resolved (string)
+        @param unresolve flag indicating to mark the file/directory as
+            unresolved (boolean)
+        """
+        args = self.initCommand("resolve")
+        if unresolve:
+            args.append("--unmark")
+        else:
+            args.append("--mark")
+        
+        if isinstance(name, list):
+            self.addArguments(args, name)
+        else:
+            args.append(name)
+        
+        title = (
+            self.tr("Marking as 'unresolved'")
+            if unresolve else
+            self.tr("Marking as 'resolved'")
+        )
+        dia = HgDialog(title, self)
+        res = dia.startProcess(args)
+        if res:
+            dia.exec()
+        self.checkVCSStatus()
+    
+    def hgAbortMerge(self):
+        """
+        Public method to abort an uncommitted merge.
+        
+        @return flag indicating, that the abortion contained an add
+            or delete (boolean)
+        """
+        if self.version >= (4, 5, 0):
+            args = self.initCommand("merge")
+            args.append("--abort")
+        else:
+            args = self.initCommand("update")
+            args.append("--clean")
+        
+        dia = HgDialog(
+            self.tr('Aborting uncommitted merge'),
+            self)
+        res = dia.startProcess(args, showArgs=False)
+        if res:
+            dia.exec()
+            res = dia.hasAddOrDelete()
+        self.checkVCSStatus()
+        return res
+    
+    def hgBranch(self):
+        """
+        Public method used to create a branch in the Mercurial repository.
+        """
+        from .HgBranchInputDialog import HgBranchInputDialog
+        dlg = HgBranchInputDialog(self.hgGetBranchesList())
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            name, commit, force = dlg.getData()
+            name = name.strip().replace(" ", "_")
+            args = self.initCommand("branch")
+            if force:
+                args.append("--force")
+            args.append(name)
+            
+            dia = HgDialog(
+                self.tr('Creating branch in the Mercurial repository'),
+                self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+                if commit:
+                    project = ericApp().getObject("Project")
+                    self.vcsCommit(
+                        project.getProjectPath(),
+                        self.tr("Created new branch <{0}>.").format(
+                            name))
+    
+    def hgShowBranch(self):
+        """
+        Public method used to show the current branch of the working directory.
+        """
+        args = self.initCommand("branch")
+        
+        dia = HgDialog(self.tr('Showing current branch'), self)
+        res = dia.startProcess(args, showArgs=False)
+        if res:
+            dia.exec()
+    
+    def hgGetCurrentBranch(self):
+        """
+        Public method to get the current branch of the working directory.
+        
+        @return name of the current branch
+        @rtype str
+        """
+        args = self.initCommand("branch")
+        
+        output, error = self.__client.runcommand(args)
+        
+        return output.strip()
+    
+    def hgEditUserConfig(self):
+        """
+        Public method used to edit the user configuration file.
+        """
+        from .HgUserConfigDialog import HgUserConfigDialog
+        dlg = HgUserConfigDialog(version=self.version)
+        dlg.exec()
+    
+    def hgEditConfig(self, repoName=None,
+                     withLargefiles=True, largefilesData=None):
+        """
+        Public method used to edit the repository configuration file.
+        
+        @param repoName directory name containing the repository
+        @type str
+        @param withLargefiles flag indicating to configure the largefiles
+            section
+        @type bool
+        @param largefilesData dictionary with data for the largefiles
+            section of the data dialog
+        @type dict
+        """
+        if repoName is None:
+            repoName = self.getClient().getRepository()
+        
+        cfgFile = os.path.join(repoName, self.adminDir, "hgrc")
+        if not os.path.exists(cfgFile):
+            # open dialog to enter the initial data
+            withLargefiles = (self.isExtensionActive("largefiles") and
+                              withLargefiles)
+            from .HgRepoConfigDataDialog import HgRepoConfigDataDialog
+            dlg = HgRepoConfigDataDialog(withLargefiles=withLargefiles,
+                                         largefilesData=largefilesData)
+            if dlg.exec() == QDialog.DialogCode.Accepted:
+                createContents = True
+                defaultUrl, defaultPushUrl = dlg.getData()
+                if withLargefiles:
+                    lfMinSize, lfPattern = dlg.getLargefilesData()
+            else:
+                createContents = False
+            with contextlib.suppress(OSError):
+                with open(cfgFile, "w") as cfg:
+                    if createContents:
+                        # write the data entered
+                        cfg.write("[paths]\n")
+                        if defaultUrl:
+                            cfg.write("default = {0}\n".format(defaultUrl))
+                        if defaultPushUrl:
+                            cfg.write("default-push = {0}\n".format(
+                                defaultPushUrl))
+                        if (
+                            withLargefiles and
+                            (lfMinSize, lfPattern) != (None, None)
+                        ):
+                            cfg.write("\n[largefiles]\n")
+                            if lfMinSize is not None:
+                                cfg.write("minsize = {0}\n".format(lfMinSize))
+                            if lfPattern is not None:
+                                cfg.write("patterns =\n")
+                                cfg.write("  {0}\n".format(
+                                    "\n  ".join(lfPattern)))
+                self.__monitorRepoIniFile(repoName)
+                self.__iniFileChanged(cfgFile)
+        self.repoEditor = MiniEditor(cfgFile, "Properties")
+        self.repoEditor.show()
+    
+    def hgVerify(self):
+        """
+        Public method to verify the integrity of the repository.
+        """
+        args = self.initCommand("verify")
+        
+        dia = HgDialog(
+            self.tr('Verifying the integrity of the Mercurial repository'),
+            self)
+        res = dia.startProcess(args)
+        if res:
+            dia.exec()
+    
+    def hgShowConfig(self):
+        """
+        Public method to show the combined configuration.
+        """
+        args = self.initCommand("showconfig")
+        args.append("--untrusted")
+        
+        dia = HgDialog(
+            self.tr('Showing the combined configuration settings'),
+            self)
+        res = dia.startProcess(args, showArgs=False)
+        if res:
+            dia.exec()
+    
+    def hgShowPaths(self):
+        """
+        Public method to show the path aliases for remote repositories.
+        """
+        args = self.initCommand("paths")
+        
+        dia = HgDialog(
+            self.tr('Showing aliases for remote repositories'),
+            self)
+        res = dia.startProcess(args, showArgs=False)
+        if res:
+            dia.exec()
+    
+    def hgRecover(self):
+        """
+        Public method to recover an interrupted transaction.
+        """
+        args = self.initCommand("recover")
+        
+        dia = HgDialog(
+            self.tr('Recovering from interrupted transaction'),
+            self)
+        res = dia.startProcess(args, showArgs=False)
+        if res:
+            dia.exec()
+    
+    def hgIdentify(self):
+        """
+        Public method to identify the current working directory.
+        """
+        args = self.initCommand("identify")
+        
+        dia = HgDialog(self.tr('Identifying project directory'), self)
+        res = dia.startProcess(args, showArgs=False)
+        if res:
+            dia.exec()
+    
+    def hgCreateIgnoreFile(self, name, autoAdd=False):
+        """
+        Public method to create the ignore file.
+        
+        @param name directory name to create the ignore file in (string)
+        @param autoAdd flag indicating to add it automatically (boolean)
+        @return flag indicating success
+        """
+        status = False
+        ignorePatterns = [
+            "glob:.eric6project",
+            "glob:.eric7project",
+            "glob:.ropeproject",
+            "glob:.jedi",
+            "glob:.directory",
+            "glob:**.pyc",
+            "glob:**.pyo",
+            "glob:**.orig",
+            "glob:**.bak",
+            "glob:**.rej",
+            "glob:**~",
+            "glob:cur",
+            "glob:tmp",
+            "glob:__pycache__",
+            "glob:**.DS_Store",
+        ]
+        
+        ignoreName = os.path.join(name, Hg.IgnoreFileName)
+        res = (
+            EricMessageBox.yesNo(
+                self.__ui,
+                self.tr("Create .hgignore file"),
+                self.tr("""<p>The file <b>{0}</b> exists already."""
+                        """ Overwrite it?</p>""").format(ignoreName),
+                icon=EricMessageBox.Warning)
+            if os.path.exists(ignoreName) else
+            True
+        )
+        if res:
+            try:
+                # create a .hgignore file
+                with open(ignoreName, "w") as ignore:
+                    ignore.write("\n".join(ignorePatterns))
+                    ignore.write("\n")
+                status = True
+            except OSError:
+                status = False
+            
+            if status and autoAdd:
+                self.vcsAdd(ignoreName, noDialog=True)
+                project = ericApp().getObject("Project")
+                project.appendFile(ignoreName)
+        
+        return status
+    
+    def hgBundle(self, bundleData=None):
+        """
+        Public method to create a changegroup file.
+        
+        @param bundleData dictionary containing the bundle creation information
+        @type dict
+        """
+        if bundleData is None:
+            from .HgBundleDialog import HgBundleDialog
+            dlg = HgBundleDialog(self.hgGetTagsList(),
+                                 self.hgGetBranchesList(),
+                                 self.hgGetBookmarksList(),
+                                 version=self.version)
+            if dlg.exec() != QDialog.DialogCode.Accepted:
+                return
+            
+            revs, baseRevs, compression, bundleAll = dlg.getParameters()
+        else:
+            revs = bundleData["revs"]
+            if bundleData["base"]:
+                baseRevs = [bundleData["base"]]
+            else:
+                baseRevs = []
+            compression = ""
+            bundleAll = bundleData["all"]
+        
+        fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
+            None,
+            self.tr("Create changegroup"),
+            self.__lastChangeGroupPath,
+            self.tr("Mercurial Changegroup Files (*.hg)"),
+            None,
+            EricFileDialog.DontConfirmOverwrite)
+        
+        if not fname:
+            return  # user aborted
+        
+        fpath = pathlib.Path(fname)
+        if not fpath.suffix:
+            ex = selectedFilter.split("(*")[1].split(")")[0]
+            if ex:
+                fpath = fpath.with_suffix(ex)
+        if fpath.exists():
+            res = EricMessageBox.yesNo(
+                self.__ui,
+                self.tr("Create changegroup"),
+                self.tr("<p>The Mercurial changegroup file <b>{0}</b> "
+                        "already exists. Overwrite it?</p>")
+                    .format(fpath),
+                icon=EricMessageBox.Warning)
+            if not res:
+                return
+        
+        self.__lastChangeGroupPath = str(fpath.parent)
+        
+        args = self.initCommand("bundle")
+        if bundleAll:
+            args.append("--all")
+        for rev in revs:
+            args.append("--rev")
+            args.append(rev)
+        for baseRev in baseRevs:
+            args.append("--base")
+            args.append(baseRev)
+        if compression:
+            args.append("--type")
+            args.append(compression)
+        args.append(str(fpath))
+        
+        dia = HgDialog(self.tr('Create changegroup'), self)
+        res = dia.startProcess(args)
+        if res:
+            dia.exec()
+    
+    def hgPreviewBundle(self):
+        """
+        Public method used to view the log of incoming changes from a
+        changegroup file.
+        """
+        file = EricFileDialog.getOpenFileName(
+            None,
+            self.tr("Preview changegroup"),
+            self.__lastChangeGroupPath,
+            self.tr("Mercurial Changegroup Files (*.hg);;All Files (*)"))
+        if file:
+            self.__lastChangeGroupPath = os.path.dirname(file)
+            
+            if self.logBrowserIncoming is None:
+                from .HgLogBrowserDialog import HgLogBrowserDialog
+                self.logBrowserIncoming = HgLogBrowserDialog(
+                    self, mode="incoming")
+            self.logBrowserIncoming.show()
+            self.logBrowserIncoming.raise_()
+            self.logBrowserIncoming.start(bundle=file)
+    
+    def hgUnbundle(self, files=None):
+        """
+        Public method to apply changegroup files.
+        
+        @param files list of bundle files to be applied
+        @type list of str
+        @return flag indicating, that the update contained an add
+            or delete
+        @rtype bool
+        """
+        res = False
+        if not files:
+            files = EricFileDialog.getOpenFileNames(
+                None,
+                self.tr("Apply changegroups"),
+                self.__lastChangeGroupPath,
+                self.tr("Mercurial Changegroup Files (*.hg);;All Files (*)"))
+        
+        if files:
+            self.__lastChangeGroupPath = os.path.dirname(files[0])
+            
+            update = EricMessageBox.yesNo(
+                self.__ui,
+                self.tr("Apply changegroups"),
+                self.tr("""Shall the working directory be updated?"""),
+                yesDefault=True)
+            
+            args = self.initCommand("unbundle")
+            if update:
+                args.append("--update")
+                args.append("--verbose")
+            args.extend(files)
+            
+            dia = HgDialog(self.tr('Apply changegroups'), self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+                res = dia.hasAddOrDelete()
+            self.checkVCSStatus()
+        
+        return res
+    
+    def hgBisect(self, subcommand):
+        """
+        Public method to perform bisect commands.
+        
+        @param subcommand name of the subcommand (one of 'good', 'bad',
+            'skip' or 'reset')
+        @type str
+        @exception ValueError raised to indicate an invalid bisect subcommand
+        """
+        if subcommand not in ("good", "bad", "skip", "reset"):
+            raise ValueError(
+                self.tr("Bisect subcommand ({0}) invalid.")
+                    .format(subcommand))
+        
+        rev = ""
+        if subcommand in ("good", "bad", "skip"):
+            from .HgRevisionSelectionDialog import HgRevisionSelectionDialog
+            dlg = HgRevisionSelectionDialog(
+                self.hgGetTagsList(),
+                self.hgGetBranchesList(),
+                bookmarksList=self.hgGetBookmarksList()
+            )
+            if dlg.exec() == QDialog.DialogCode.Accepted:
+                rev = dlg.getRevision()
+            else:
+                return
+        
+        args = self.initCommand("bisect")
+        args.append("--{0}".format(subcommand))
+        if rev:
+            args.append(rev)
+        
+        dia = HgDialog(
+            self.tr('Mercurial Bisect ({0})').format(subcommand), self)
+        res = dia.startProcess(args)
+        if res:
+            dia.exec()
+    
+    def vcsForget(self, name):
+        """
+        Public method used to remove a file from the Mercurial repository.
+        
+        This will not remove the file from the project directory.
+        
+        @param name file/directory name to be removed
+        @type str or list of str
+        """
+        args = self.initCommand("forget")
+        args.append('-v')
+        
+        if isinstance(name, list):
+            self.addArguments(args, name)
+        else:
+            args.append(name)
+        
+        dia = HgDialog(
+            self.tr('Removing files from the Mercurial repository only'),
+            self)
+        res = dia.startProcess(args)
+        if res:
+            dia.exec()
+            if isinstance(name, list):
+                self.__forgotNames.extend(name)
+            else:
+                self.__forgotNames.append(name)
+    
+    def hgBackout(self):
+        """
+        Public method used to backout an earlier changeset from the Mercurial
+        repository.
+        """
+        from .HgBackoutDialog import HgBackoutDialog
+        dlg = HgBackoutDialog(self.hgGetTagsList(),
+                              self.hgGetBranchesList(),
+                              self.hgGetBookmarksList())
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            rev, merge, date, user, message = dlg.getParameters()
+            if not rev:
+                EricMessageBox.warning(
+                    self.__ui,
+                    self.tr("Backing out changeset"),
+                    self.tr("""No revision given. Aborting..."""))
+                return
+            
+            args = self.initCommand("backout")
+            args.append('-v')
+            if merge:
+                args.append('--merge')
+            if date:
+                args.append('--date')
+                args.append(date)
+            if user:
+                args.append('--user')
+                args.append(user)
+            args.append('--message')
+            args.append(message)
+            args.append(rev)
+            
+            dia = HgDialog(self.tr('Backing out changeset'), self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+    
+    def hgRollback(self):
+        """
+        Public method used to rollback the last transaction.
+        """
+        res = EricMessageBox.yesNo(
+            None,
+            self.tr("Rollback last transaction"),
+            self.tr("""Are you sure you want to rollback the last"""
+                    """ transaction?"""),
+            icon=EricMessageBox.Warning)
+        if res:
+            dia = HgDialog(self.tr('Rollback last transaction'), self)
+            res = dia.startProcess(["rollback"])
+            if res:
+                dia.exec()
+
+    def hgServe(self, repoPath):
+        """
+        Public method used to serve the project.
+        
+        @param repoPath directory containing the repository
+        @type str
+        """
+        from .HgServeDialog import HgServeDialog
+        self.serveDlg = HgServeDialog(self, repoPath)
+        self.serveDlg.show()
+    
+    def hgImport(self):
+        """
+        Public method to import a patch file.
+        
+        @return flag indicating, that the import contained an add, a delete
+            or a change to the project file (boolean)
+        """
+        from .HgImportDialog import HgImportDialog
+        dlg = HgImportDialog(self)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            (patchFile, noCommit, message, date, user, withSecret, stripCount,
+             force) = dlg.getParameters()
+            
+            args = self.initCommand("import")
+            args.append("--verbose")
+            if noCommit:
+                args.append("--no-commit")
+            else:
+                if message:
+                    args.append('--message')
+                    args.append(message)
+                if date:
+                    args.append('--date')
+                    args.append(date)
+                if user:
+                    args.append('--user')
+                    args.append(user)
+            if stripCount != 1:
+                args.append("--strip")
+                args.append(str(stripCount))
+            if force:
+                args.append("--force")
+            if withSecret:
+                args.append("--secret")
+            args.append(patchFile)
+            
+            dia = HgDialog(self.tr("Import Patch"), self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+                res = dia.hasAddOrDelete()
+            self.checkVCSStatus()
+        else:
+            res = False
+        
+        return res
+    
+    def hgExport(self):
+        """
+        Public method to export patches to files.
+        """
+        from .HgExportDialog import HgExportDialog
+        dlg = HgExportDialog(self.hgGetBookmarksList(),
+                             self.version >= (4, 7, 0))
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            (filePattern, revisions, bookmark, switchParent, allText,
+             noDates, git) = dlg.getParameters()
+            
+            args = self.initCommand("export")
+            args.append("--output")
+            args.append(filePattern)
+            args.append("--verbose")
+            if switchParent:
+                args.append("--switch-parent")
+            if allText:
+                args.append("--text")
+            if noDates:
+                args.append("--nodates")
+            if git:
+                args.append("--git")
+            if bookmark:
+                args.append("--bookmark")
+                args.append(bookmark)
+            else:
+                for rev in revisions:
+                    args.append(rev)
+            
+            dia = HgDialog(self.tr("Export Patches"), self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+    
+    def hgPhase(self, data=None):
+        """
+        Public method to change the phase of revisions.
+        
+        @param data tuple giving phase data (list of revisions, phase, flag
+            indicating a forced operation) (list of strings, string, boolean)
+        @return flag indicating success (boolean)
+        @exception ValueError raised to indicate an invalid phase
+        """
+        if data is None:
+            from .HgPhaseDialog import HgPhaseDialog
+            dlg = HgPhaseDialog()
+            if dlg.exec() == QDialog.DialogCode.Accepted:
+                data = dlg.getData()
+        
+        if data:
+            revs, phase, force = data
+            
+            if phase not in ("p", "d", "s"):
+                raise ValueError("Invalid phase given.")
+            
+            args = self.initCommand("phase")
+            if phase == "p":
+                args.append("--public")
+            elif phase == "d":
+                args.append("--draft")
+            else:
+                args.append("--secret")
+            
+            if force:
+                args.append("--force")
+            for rev in revs:
+                args.append(rev)
+            
+            dia = HgDialog(self.tr("Change Phase"), self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+                res = dia.normalExitWithoutErrors()
+        else:
+            res = False
+        
+        return res
+    
+    def hgGraft(self, revs=None):
+        """
+        Public method to copy changesets from another branch.
+        
+        @param revs list of revisions to show in the revisions pane (list of
+            strings)
+        @return flag indicating that the project should be reread (boolean)
+        """
+        from .HgGraftDialog import HgGraftDialog
+        res = False
+        dlg = HgGraftDialog(self, revs)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            (revs,
+             (userData, currentUser, userName),
+             (dateData, currentDate, dateStr),
+             log, dryrun, noCommit) = dlg.getData()
+            
+            args = self.initCommand("graft")
+            args.append("--verbose")
+            if userData:
+                if currentUser:
+                    args.append("--currentuser")
+                else:
+                    args.append("--user")
+                    args.append(userName)
+            if dateData:
+                if currentDate:
+                    args.append("--currentdate")
+                else:
+                    args.append("--date")
+                    args.append(dateStr)
+            if log:
+                args.append("--log")
+            if dryrun:
+                args.append("--dry-run")
+            if noCommit:
+                args.append("--no-commit")
+            args.extend(revs)
+            
+            dia = HgDialog(self.tr('Copy Changesets'), self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+                res = dia.hasAddOrDelete()
+                self.checkVCSStatus()
+        return res
+    
+    def __hgGraftSubCommand(self, subcommand, title):
+        """
+        Private method to perform a Mercurial graft subcommand.
+        
+        @param subcommand subcommand flag
+        @type str
+        @param title tirle of the dialog
+        @type str
+        @return flag indicating that the project should be reread
+        @rtype bool
+        """
+        args = self.initCommand("graft")
+        args.append(subcommand)
+        args.append("--verbose")
+        
+        dia = HgDialog(title, self)
+        res = dia.startProcess(args)
+        if res:
+            dia.exec()
+            res = dia.hasAddOrDelete()
+            self.checkVCSStatus()
+        return res
+    
+    def hgGraftContinue(self, path):
+        """
+        Public method to continue copying changesets from another branch.
+        
+        @param path directory name of the project
+        @type str
+        @return flag indicating that the project should be reread
+        @rtype bool
+        """
+        return self.__hgGraftSubCommand(
+            "--continue", self.tr('Copy Changesets (Continue)'))
+    
+    def hgGraftStop(self, path):
+        """
+        Public method to stop an interrupted copying session.
+        
+        @param path directory name of the project
+        @type str
+        @return flag indicating that the project should be reread
+        @rtype bool
+        """
+        return self.__hgGraftSubCommand(
+            "--stop", self.tr('Copy Changesets (Stop)'))
+    
+    def hgGraftAbort(self, path):
+        """
+        Public method to abort an interrupted copying session and perform
+        a rollback.
+        
+        @param path directory name of the project
+        @type str
+        @return flag indicating that the project should be reread
+        @rtype bool
+        """
+        return self.__hgGraftSubCommand(
+            "--abort", self.tr('Copy Changesets (Abort)'))
+    
+    def hgArchive(self):
+        """
+        Public method to create an unversioned archive from the repository.
+        """
+        from .HgArchiveDialog import HgArchiveDialog
+        dlg = HgArchiveDialog(self)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            archive, type_, prefix, subrepos = dlg.getData()
+            
+            args = self.initCommand("archive")
+            if type_:
+                args.append("--type")
+                args.append(type_)
+            if prefix:
+                args.append("--prefix")
+                args.append(prefix)
+            if subrepos:
+                args.append("--subrepos")
+            args.append(archive)
+            
+            dia = HgDialog(self.tr("Create Unversioned Archive"), self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+    
+    def hgDeleteBackups(self):
+        """
+        Public method to delete all backup bundles in the backup area.
+        """
+        backupdir = os.path.join(self.getClient().getRepository(),
+                                 self.adminDir, "strip-backup")
+        yes = EricMessageBox.yesNo(
+            self.__ui,
+            self.tr("Delete All Backups"),
+            self.tr(
+                """<p>Do you really want to delete all backup bundles"""
+                """ stored in the backup area?<br/><b>{0}</b></p>"""
+            ).format(backupdir)
+        )
+        if yes:
+            shutil.rmtree(backupdir, True)
+    
+    ###########################################################################
+    ## Methods to deal with sub-repositories are below.
+    ###########################################################################
+    
+    def getHgSubPath(self):
+        """
+        Public method to get the path to the .hgsub file containing the
+        definitions of sub-repositories.
+        
+        @return full path of the .hgsub file (string)
+        """
+        ppath = self.__projectHelper.getProject().getProjectPath()
+        return os.path.join(ppath, ".hgsub")
+    
+    def hasSubrepositories(self):
+        """
+        Public method to check, if the project might have sub-repositories.
+        
+        @return flag indicating the existence of sub-repositories (boolean)
+        """
+        hgsub = self.getHgSubPath()
+        return os.path.isfile(hgsub) and os.stat(hgsub).st_size > 0
+    
+    def hgAddSubrepository(self):
+        """
+        Public method to add a sub-repository.
+        """
+        from .HgAddSubrepositoryDialog import HgAddSubrepositoryDialog
+        ppath = self.__projectHelper.getProject().getProjectPath()
+        hgsub = self.getHgSubPath()
+        dlg = HgAddSubrepositoryDialog(ppath)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            relPath, subrepoType, subrepoUrl = dlg.getData()
+            if subrepoType == "hg":
+                url = subrepoUrl
+            else:
+                url = "[{0}]{1}".format(subrepoType, subrepoUrl)
+            entry = "{0} = {1}\n".format(relPath, url)
+            
+            contents = []
+            if os.path.isfile(hgsub):
+                # file exists; check, if such an entry exists already
+                needsAdd = False
+                try:
+                    with open(hgsub, "r") as f:
+                        contents = f.readlines()
+                except OSError as err:
+                    EricMessageBox.critical(
+                        self.__ui,
+                        self.tr("Add Sub-repository"),
+                        self.tr(
+                            """<p>The sub-repositories file .hgsub could not"""
+                            """ be read.</p><p>Reason: {0}</p>""")
+                        .format(str(err)))
+                    return
+                
+                if entry in contents:
+                    EricMessageBox.critical(
+                        self.__ui,
+                        self.tr("Add Sub-repository"),
+                        self.tr(
+                            """<p>The sub-repositories file .hgsub already"""
+                            """ contains an entry <b>{0}</b>."""
+                            """ Aborting...</p>""").format(entry))
+                    return
+            else:
+                needsAdd = True
+            
+            if contents and not contents[-1].endswith("\n"):
+                contents[-1] = contents[-1] + "\n"
+            contents.append(entry)
+            try:
+                with open(hgsub, "w") as f:
+                    f.writelines(contents)
+            except OSError as err:
+                EricMessageBox.critical(
+                    self.__ui,
+                    self.tr("Add Sub-repository"),
+                    self.tr(
+                        """<p>The sub-repositories file .hgsub could not"""
+                        """ be written to.</p><p>Reason: {0}</p>""")
+                    .format(str(err)))
+                return
+            
+            if needsAdd:
+                self.vcsAdd(hgsub)
+                self.__projectHelper.getProject().appendFile(hgsub)
+    
+    def hgRemoveSubrepositories(self):
+        """
+        Public method to remove sub-repositories.
+        """
+        hgsub = self.getHgSubPath()
+        
+        subrepositories = []
+        if not os.path.isfile(hgsub):
+            EricMessageBox.critical(
+                self.__ui,
+                self.tr("Remove Sub-repositories"),
+                self.tr("""<p>The sub-repositories file .hgsub does not"""
+                        """ exist. Aborting...</p>"""))
+            return
+            
+        try:
+            with open(hgsub, "r") as f:
+                subrepositories = [line.strip() for line in f.readlines()]
+        except OSError as err:
+            EricMessageBox.critical(
+                self.__ui,
+                self.tr("Remove Sub-repositories"),
+                self.tr("""<p>The sub-repositories file .hgsub could not"""
+                        """ be read.</p><p>Reason: {0}</p>""")
+                .format(str(err)))
+            return
+        
+        from .HgRemoveSubrepositoriesDialog import (
+            HgRemoveSubrepositoriesDialog
+        )
+        dlg = HgRemoveSubrepositoriesDialog(subrepositories)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            subrepositories, removedSubrepos, deleteSubrepos = dlg.getData()
+            contents = "\n".join(subrepositories) + "\n"
+            try:
+                with open(hgsub, "w") as f:
+                    f.write(contents)
+            except OSError as err:
+                EricMessageBox.critical(
+                    self.__ui,
+                    self.tr("Remove Sub-repositories"),
+                    self.tr(
+                        """<p>The sub-repositories file .hgsub could not"""
+                        """ be written to.</p><p>Reason: {0}</p>""")
+                    .format(str(err)))
+                return
+            
+            if deleteSubrepos:
+                ppath = self.__projectHelper.getProject().getProjectPath()
+                for removedSubrepo in removedSubrepos:
+                    subrepoPath = removedSubrepo.split("=", 1)[0].strip()
+                    subrepoAbsPath = os.path.join(ppath, subrepoPath)
+                    shutil.rmtree(subrepoAbsPath, True)
+    
+    ###########################################################################
+    ## Methods to handle configuration dependent stuff are below.
+    ###########################################################################
+    
+    def __checkDefaults(self):
+        """
+        Private method to check, if the default and default-push URLs
+        have been configured.
+        """
+        args = self.initCommand("showconfig")
+        args.append('paths')
+        
+        output, error = self.__client.runcommand(args)
+        
+        self.__defaultConfigured = False
+        self.__defaultPushConfigured = False
+        if output:
+            for line in output.splitlines():
+                line = line.strip()
+                if (
+                    line.startswith("paths.default=") and
+                    not line.endswith("=")
+                ):
+                    self.__defaultConfigured = True
+                if (
+                    line.startswith("paths.default-push=") and
+                    not line.endswith("=")
+                ):
+                    self.__defaultPushConfigured = True
+    
+    def canCommitMerge(self):
+        """
+        Public method to check, if the working directory is an uncommitted
+        merge.
+ 
+        @return flag indicating commit merge capability
+        @rtype bool
+        """
+        args = self.initCommand("identify")
+        
+        output, error = self.__client.runcommand(args)
+        
+        return output.count('+') == 2
+
+    def canPull(self):
+        """
+        Public method to check, if pull is possible.
+        
+        @return flag indicating pull capability (boolean)
+        """
+        return self.__defaultConfigured
+    
+    def canPush(self):
+        """
+        Public method to check, if push is possible.
+        
+        @return flag indicating push capability (boolean)
+        """
+        return self.__defaultPushConfigured or self.__defaultConfigured
+    
+    def __iniFileChanged(self, path):
+        """
+        Private slot to handle a change of the Mercurial configuration file.
+        
+        @param path name of the changed file (string)
+        """
+        if self.__client:
+            ok, err = self.__client.restartServer()
+            if not ok:
+                EricMessageBox.warning(
+                    None,
+                    self.tr("Mercurial Command Server"),
+                    self.tr(
+                        """<p>The Mercurial Command Server could not be"""
+                        """ restarted.</p><p>Reason: {0}</p>""").format(err))
+        
+        self.__getExtensionsInfo()
+        
+        if self.__repoIniFile and path == self.__repoIniFile:
+            self.__checkDefaults()
+        
+        self.iniFileChanged.emit()
+    
+    def __monitorRepoIniFile(self, repodir):
+        """
+        Private slot to add a repository configuration file to the list of
+        monitored files.
+        
+        @param repodir directory name of the repository
+        @type str
+        """
+        cfgFile = os.path.join(repodir, self.adminDir, "hgrc")
+        if os.path.exists(cfgFile):
+            self.__iniWatcher.addPath(cfgFile)
+            self.__repoIniFile = cfgFile
+            self.__checkDefaults()
+    
+    ###########################################################################
+    ## Methods to handle extensions are below.
+    ###########################################################################
+    
+    def __getExtensionsInfo(self):
+        """
+        Private method to get the active extensions from Mercurial.
+        """
+        activeExtensions = sorted(self.__activeExtensions)
+        self.__activeExtensions = []
+        
+        args = self.initCommand("showconfig")
+        args.append('extensions')
+        
+        output, error = self.__client.runcommand(args)
+        
+        if output:
+            for line in output.splitlines():
+                extensionName = (
+                    line.split("=", 1)[0].strip().split(".")[-1].strip()
+                )
+                self.__activeExtensions.append(extensionName)
+        if self.version < (4, 8, 0) and "closehead" in self.__activeExtensions:
+            self.__activeExtensions.remove["closehead"]
+        
+        if activeExtensions != sorted(self.__activeExtensions):
+            self.activeExtensionsChanged.emit()
+    
+    def isExtensionActive(self, extensionName):
+        """
+        Public method to check, if an extension is active.
+        
+        @param extensionName name of the extension to check for (string)
+        @return flag indicating an active extension (boolean)
+        """
+        extensionName = extensionName.strip()
+        isActive = extensionName in self.__activeExtensions
+        
+        return isActive
+    
+    def getExtensionObject(self, extensionName):
+        """
+        Public method to get a reference to an extension object.
+        
+        @param extensionName name of the extension (string)
+        @return reference to the extension object (boolean)
+        """
+        return self.__extensions[extensionName]
+    
+    ###########################################################################
+    ## Methods to get the helper objects are below.
+    ###########################################################################
+    
+    def vcsGetProjectBrowserHelper(self, browser, project,
+                                   isTranslationsBrowser=False):
+        """
+        Public method to instantiate a helper object for the different
+        project browsers.
+        
+        @param browser reference to the project browser object
+        @param project reference to the project object
+        @param isTranslationsBrowser flag indicating, the helper is requested
+            for the translations browser (this needs some special treatment)
+        @return the project browser helper object
+        """
+        from .ProjectBrowserHelper import HgProjectBrowserHelper
+        return HgProjectBrowserHelper(self, browser, project,
+                                      isTranslationsBrowser)
+        
+    def vcsGetProjectHelper(self, project):
+        """
+        Public method to instantiate a helper object for the project.
+        
+        @param project reference to the project object
+        @return the project helper object
+        """
+        # find the root of the repo
+        repodir = project.getProjectPath()
+        while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+            repodir = os.path.dirname(repodir)
+            if not repodir or os.path.splitdrive(repodir)[1] == os.sep:
+                repodir = ""
+                break
+        
+        self.__projectHelper = self.__plugin.getProjectHelper()
+        self.__projectHelper.setObjects(self, project)
+        
+        if repodir:
+            self.__repoDir = repodir
+            self.__createClient(repodir)
+            self.__monitorRepoIniFile(repodir)
+        
+        return self.__projectHelper
+    
+    ###########################################################################
+    ## Methods to handle the Mercurial command server are below.
+    ###########################################################################
+    
+    def __createClient(self, repodir=""):
+        """
+        Private method to create a Mercurial command server client.
+        
+        @param repodir path of the local repository
+        @type str
+        """
+        self.stopClient()
+        
+        self.__client = HgClient(repodir, "utf-8", self)
+        ok, err = self.__client.startServer()
+        if not ok:
+            EricMessageBox.warning(
+                None,
+                self.tr("Mercurial Command Server"),
+                self.tr(
+                    """<p>The Mercurial Command Server could not be"""
+                    """ started.</p><p>Reason: {0}</p>""").format(err))
+    
+    def getClient(self):
+        """
+        Public method to get a reference to the command server interface.
+        
+        @return reference to the client (HgClient)
+        """
+        if self.__client is None:
+            self.__createClient(self.__repoDir)
+        
+        return self.__client
+    
+    def stopClient(self):
+        """
+        Public method to stop the command server client.
+        """
+        if self.__client is not None:
+            self.__client.stopServer()
+            self.__client = None
+    
+    ###########################################################################
+    ## Status Monitor Thread methods
+    ###########################################################################
+
+    def _createStatusMonitorThread(self, interval, project):
+        """
+        Protected method to create an instance of the VCS status monitor
+        thread.
+        
+        @param interval check interval for the monitor thread in seconds
+            (integer)
+        @param project reference to the project object (Project)
+        @return reference to the monitor thread (QThread)
+        """
+        from .HgStatusMonitorThread import HgStatusMonitorThread
+        return HgStatusMonitorThread(interval, project, self)
+
+    ###########################################################################
+    ## Bookmarks methods
+    ###########################################################################
+
+    def hgListBookmarks(self):
+        """
+        Public method used to list the available bookmarks.
+        """
+        self.bookmarksList = []
+        
+        if self.bookmarksListDlg is None:
+            from .HgBookmarksListDialog import HgBookmarksListDialog
+            self.bookmarksListDlg = HgBookmarksListDialog(self)
+        self.bookmarksListDlg.show()
+        self.bookmarksListDlg.raise_()
+        self.bookmarksListDlg.start(self.bookmarksList)
+    
+    def hgGetBookmarksList(self):
+        """
+        Public method to get the list of bookmarks.
+        
+        @return list of bookmarks (list of string)
+        """
+        args = self.initCommand("bookmarks")
+        
+        client = self.getClient()
+        output = client.runcommand(args)[0]
+        
+        self.bookmarksList = []
+        for line in output.splitlines():
+            li = line.strip().split()
+            if li[-1][0] in "1234567890":
+                # last element is a rev:changeset
+                del li[-1]
+                if li[0] == "*":
+                    del li[0]
+                name = " ".join(li)
+                self.bookmarksList.append(name)
+        
+        return self.bookmarksList[:]
+    
+    def hgBookmarkDefine(self, revision=None, bookmark=None):
+        """
+        Public method to define a bookmark.
+        
+        @param revision revision to set bookmark for (string)
+        @param bookmark name of the bookmark (string)
+        """
+        if bool(revision) and bool(bookmark):
+            ok = True
+        else:
+            from .HgBookmarkDialog import HgBookmarkDialog
+            dlg = HgBookmarkDialog(HgBookmarkDialog.DEFINE_MODE,
+                                   self.hgGetTagsList(),
+                                   self.hgGetBranchesList(),
+                                   self.hgGetBookmarksList())
+            if dlg.exec() == QDialog.DialogCode.Accepted:
+                revision, bookmark = dlg.getData()
+                ok = True
+            else:
+                ok = False
+        
+        if ok:
+            args = self.initCommand("bookmarks")
+            if revision:
+                args.append("--rev")
+                args.append(revision)
+            args.append(bookmark)
+            
+            dia = HgDialog(self.tr('Mercurial Bookmark'), self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+    
+    def hgBookmarkDelete(self, bookmark=None):
+        """
+        Public method to delete a bookmark.
+        
+        @param bookmark name of the bookmark (string)
+        """
+        if bookmark:
+            ok = True
+        else:
+            bookmark, ok = QInputDialog.getItem(
+                None,
+                self.tr("Delete Bookmark"),
+                self.tr("Select the bookmark to be deleted:"),
+                [""] + sorted(self.hgGetBookmarksList()),
+                0, True)
+        if ok and bookmark:
+            args = self.initCommand("bookmarks")
+            args.append("--delete")
+            args.append(bookmark)
+            
+            dia = HgDialog(self.tr('Delete Mercurial Bookmark'), self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+    
+    def hgBookmarkRename(self, renameInfo=None):
+        """
+        Public method to rename a bookmark.
+        
+        @param renameInfo old and new names of the bookmark
+        @type tuple of str and str
+        """
+        if not renameInfo:
+            from .HgBookmarkRenameDialog import HgBookmarkRenameDialog
+            dlg = HgBookmarkRenameDialog(self.hgGetBookmarksList())
+            if dlg.exec() == QDialog.DialogCode.Accepted:
+                renameInfo = dlg.getData()
+        
+        if renameInfo:
+            args = self.initCommand("bookmarks")
+            args.append("--rename")
+            args.append(renameInfo[0])
+            args.append(renameInfo[1])
+            
+            dia = HgDialog(self.tr('Rename Mercurial Bookmark'), self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+    
+    def hgBookmarkMove(self, revision=None, bookmark=None):
+        """
+        Public method to move a bookmark.
+        
+        @param revision revision to set bookmark for (string)
+        @param bookmark name of the bookmark (string)
+        """
+        if bool(revision) and bool(bookmark):
+            ok = True
+        else:
+            from .HgBookmarkDialog import HgBookmarkDialog
+            dlg = HgBookmarkDialog(HgBookmarkDialog.MOVE_MODE,
+                                   self.hgGetTagsList(),
+                                   self.hgGetBranchesList(),
+                                   self.hgGetBookmarksList())
+            if dlg.exec() == QDialog.DialogCode.Accepted:
+                revision, bookmark = dlg.getData()
+                ok = True
+            else:
+                ok = False
+        
+        if ok:
+            args = self.initCommand("bookmarks")
+            args.append("--force")
+            if revision:
+                args.append("--rev")
+                args.append(revision)
+            args.append(bookmark)
+            
+            dia = HgDialog(self.tr('Move Mercurial Bookmark'), self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+    
+    def hgBookmarkIncoming(self):
+        """
+        Public method to show a list of incoming bookmarks.
+        """
+        from .HgBookmarksInOutDialog import HgBookmarksInOutDialog
+        self.bookmarksInOutDlg = HgBookmarksInOutDialog(
+            self, HgBookmarksInOutDialog.INCOMING)
+        self.bookmarksInOutDlg.show()
+        self.bookmarksInOutDlg.start()
+    
+    def hgBookmarkOutgoing(self):
+        """
+        Public method to show a list of outgoing bookmarks.
+        """
+        from .HgBookmarksInOutDialog import HgBookmarksInOutDialog
+        self.bookmarksInOutDlg = HgBookmarksInOutDialog(
+            self, HgBookmarksInOutDialog.OUTGOING)
+        self.bookmarksInOutDlg.show()
+        self.bookmarksInOutDlg.start()
+    
+    def __getInOutBookmarks(self, incoming):
+        """
+        Private method to get the list of incoming or outgoing bookmarks.
+        
+        @param incoming flag indicating to get incoming bookmarks (boolean)
+        @return list of bookmarks (list of string)
+        """
+        bookmarksList = []
+        
+        args = (
+            self.initCommand("incoming")
+            if incoming else
+            self.initCommand("outgoing")
+        )
+        args.append('--bookmarks')
+        
+        client = self.getClient()
+        output = client.runcommand(args)[0]
+        
+        for line in output.splitlines():
+            if line.startswith(" "):
+                li = line.strip().split()
+                del li[-1]
+                name = " ".join(li)
+                bookmarksList.append(name)
+        
+        return bookmarksList
+    
+    def hgBookmarkPull(self, current=False, bookmark=None):
+        """
+        Public method to pull a bookmark from a remote repository.
+        
+        @param current flag indicating to pull the current bookmark
+        @type bool
+        @param bookmark name of the bookmark
+        @type str
+        """
+        if current:
+            bookmark = "."
+            ok = True
+        elif bookmark:
+            ok = True
+        else:
+            bookmarks = self.__getInOutBookmarks(True)
+            bookmark, ok = QInputDialog.getItem(
+                None,
+                self.tr("Pull Bookmark"),
+                self.tr("Select the bookmark to be pulled:"),
+                [""] + sorted(bookmarks),
+                0, True)
+        
+        if ok and bookmark:
+            args = self.initCommand("pull")
+            args.append('--bookmark')
+            args.append(bookmark)
+            
+            dia = HgDialog(self.tr(
+                'Pulling bookmark from a remote Mercurial repository'),
+                self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()
+    
+    def hgBookmarkPush(self, current=False, bookmark=None, allBookmarks=False):
+        """
+        Public method to push a bookmark to a remote repository.
+        
+        @param current flag indicating to push the current bookmark
+        @type bool
+        @param bookmark name of the bookmark
+        @type str
+        @param allBookmarks flag indicating to push all bookmarks
+        @type bool
+        """
+        if current:
+            bookmark = "."
+            ok = True
+        elif bookmark or allBookmarks:
+            ok = True
+        else:
+            bookmarks = self.__getInOutBookmarks(False)
+            bookmark, ok = QInputDialog.getItem(
+                None,
+                self.tr("Push Bookmark"),
+                self.tr("Select the bookmark to be push:"),
+                [""] + sorted(bookmarks),
+                0, True)
+        
+        if ok and (bool(bookmark) or all):
+            args = self.initCommand("push")
+            if allBookmarks:
+                args.append('--all-bookmarks')
+            else:
+                args.append('--bookmark')
+                args.append(bookmark)
+            
+            dia = HgDialog(self.tr(
+                'Pushing bookmark to a remote Mercurial repository'),
+                self)
+            res = dia.startProcess(args)
+            if res:
+                dia.exec()

eric ide

mercurial