Plugins/VcsPlugins/vcsMercurial/hg.py

changeset 178
dd9f0bca5e2f
child 181
4af57f97c1bc
diff -r c822ccc4d138 -r dd9f0bca5e2f Plugins/VcsPlugins/vcsMercurial/hg.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/VcsPlugins/vcsMercurial/hg.py	Mon Apr 12 18:00:42 2010 +0000
@@ -0,0 +1,1501 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2010 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the version control systems interface to Mercurial.
+"""
+
+import os
+import shutil
+import urllib.request, urllib.parse, urllib.error
+
+from PyQt4.QtCore import QProcess, SIGNAL
+from PyQt4.QtGui import QMessageBox, QApplication, QDialog, QInputDialog
+
+from E5Gui.E5Application import e5App
+
+from VCS.VersionControl import VersionControl
+from VCS.RepositoryInfoDialog import VcsRepositoryInfoDialog
+
+from .HgDialog import HgDialog
+from .HgCommitDialog import HgCommitDialog
+from .HgOptionsDialog import HgOptionsDialog
+from .HgNewProjectOptionsDialog import HgNewProjectOptionsDialog
+from .HgCopyDialog import HgCopyDialog
+from .HgLogDialog import HgLogDialog
+from .HgLogBrowserDialog import HgLogBrowserDialog
+from .HgDiffDialog import HgDiffDialog
+from .HgRevisionsSelectionDialog import HgRevisionsSelectionDialog
+from .HgRevisionSelectionDialog import HgRevisionSelectionDialog
+from .HgMergeDialog import HgMergeDialog
+from .HgStatusMonitorThread import HgStatusMonitorThread
+from .HgStatusDialog import HgStatusDialog
+from .HgAnnotateDialog import HgAnnotateDialog
+from .HgTagDialog import HgTagDialog
+from .HgTagBranchListDialog import HgTagBranchListDialog
+from .HgCommandDialog import HgCommandDialog
+
+from .ProjectBrowserHelper import HgProjectBrowserHelper
+
+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
+    """
+    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.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.log = None
+        self.diff = None
+        self.status = None
+        self.tagbranchList = None
+        self.annotate = None
+        
+        self.statusCache = {}
+        
+        self.__commitData = {}
+        self.__commitDialog = None
+    
+    def getPlugin(self):
+        """
+        Public method to get a reference to the plugin object.
+        
+        @return reference to the plugin object (VcsMercurialPlugin)
+        """
+        return self.__plugin
+    
+    def vcsShutdown(self):
+        """
+        Public method used to shutdown the Mercurial interface.
+        """
+        if self.log is not None:
+            self.log.close()
+        if self.diff is not None:
+            self.diff.close()
+        if self.status is not None:
+            self.status.close()
+        if self.tagbranchList is not None:
+            self.tagbranchList.close()
+        if self.annotate is not None:
+            self.annotate.close()
+    
+    def vcsExists(self):
+        """
+        Public method used to test for the presence of the hg executable.
+        
+        @return flag indicating the existance (boolean) and an error message (string)
+        """
+        self.versionStr = ''
+        errMsg = ""
+        ioEncoding = Preferences.getSystem("IOEncoding")
+        
+        process = QProcess()
+        process.start('hg', ['version'])
+        procStarted = process.waitForStarted()
+        if procStarted:
+            finished = process.waitForFinished(30000)
+            if finished and process.exitCode() == 0:
+                output = \
+                    str(process.readAllStandardOutput(), ioEncoding, 'replace')
+                self.versionStr = output.splitlines()[0].split()[-1][0:-1]
+                return True, errMsg
+            else:
+                if finished:
+                    errMsg = \
+                        self.trUtf8("The hg process finished with the exit code {0}")\
+                        .format(process.exitCode())
+                else:
+                    errMsg = self.trUtf8("The hg process did not finish within 30s.")
+        else:
+            errMsg = self.trUtf8("Could not start the hg executable.")
+        
+        return False, 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):
+        """
+        Public method to convert an uncontrolled project to a version controlled project.
+        
+        @param vcsDataDict dictionary of data required for the conversion
+        @param project reference to the project object
+        """
+        success = self.vcsImport(vcsDataDict, project.ppath)[0]
+        if not success:
+            QMessageBox.critical(None,
+                self.trUtf8("Create project repository"),
+                self.trUtf8("""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):
+        """
+        Public method used to import the project into the Subversion repository.
+        
+        @param vcsDataDict dictionary of data required for the import
+        @param projectDir project directory (string)
+        @param noDialog flag indicating quiet operations
+        @return flag indicating an execution without errors (boolean)
+            and a flag indicating the version controll status (boolean)
+        """
+        ignorePatterns = [
+            "glob:.eric5project", 
+            "glob:.ropeproject", 
+            "glob:.directory", 
+            "glob:*.pyc", 
+            "glob:*.orig", 
+            "glob:*.bak", 
+        ]
+        
+        msg = vcsDataDict["message"]
+        if not msg:
+            msg = '***'
+        
+        args = []
+        args.append('init')
+        args.append(projectDir)
+        dia = HgDialog(self.trUtf8('Creating Mercurial repository'))
+        res = dia.startProcess(args)
+        if res:
+            dia.exec_()
+        status = dia.normalExit()
+        
+        if status:
+            try:
+                # create a .hgignore file
+                ignore = open(os.path.join(projectDir, ".hgignore"), "w")
+                ignore.write("\n".join(ignorePatterns))
+                ignore.close()
+            except IOError:
+                status = False
+            
+            if status:
+                args = []
+                args.append('commit')
+                args.append('--addremove')
+                args.append('--message')
+                args.append(msg)
+                args.append(projectDir)
+                dia = HgDialog(self.trUtf8('Initial commit to Mercurial repository'))
+                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"])
+        if vcsUrl.startswith('/'):
+            vcsUrl = 'file://%s' % vcsUrl
+        elif vcsUrl[1] in ['|', ':']:
+            vcsUrl = 'file:///%s' % vcsUrl
+        
+        args = []
+        args.append('clone')
+        self.addArguments(args, self.options['global'])
+        self.addArguments(args, self.options['checkout'])
+        if rev:
+            args.append("--rev")
+            args.append(rev)
+        args.append(self.__hgURL(vcsUrl))
+        args.append(projectDir)
+        
+        if noDialog:
+            return self.startSynchronizedProcess(QProcess(), 'hg', args)
+        else:
+            dia = HgDialog(self.trUtf8('Cloning project from a Mercurial repository'))
+            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 Subversion repository.
+        
+        @param vcsDataDict dictionary of data required for the checkout
+        @param projectDir project directory to create (string)
+        @return flag indicating an execution without errors (boolean)
+        """
+        status = self.vcsCheckout(vcsDataDict, projectDir)
+        shutil.rmtree(os.path.join(projectDir, self.adminDir), True)
+        if os.path.exists(os.path.join(projectDir, '.hgignore')):
+            os.remove(os.path.join(projectDir, '.hgignore'))
+        return status
+    
+    def vcsCommit(self, name, message, noDialog = False, closeBranch = 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
+        @keyparam closeBranch flag indicating a close branch commit (boolean)
+        """
+        msg = message
+        
+        if not noDialog and not msg:
+            # call CommitDialog and get message from there
+            if self.__commitDialog is None:
+                self.__commitDialog = HgCommitDialog(self, self.__ui)
+                self.connect(self.__commitDialog, SIGNAL("accepted()"), 
+                             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
+        
+        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"]
+        
+        if self.__commitDialog is not None:
+            msg = self.__commitDialog.logMessage()
+            self.disconnect(self.__commitDialog, SIGNAL("accepted()"), 
+                            self.__vcsCommit_Step2)
+            self.__commitDialog = None
+        
+        if not msg:
+            msg = '***'
+        
+        args = []
+        args.append('commit')
+        self.addArguments(args, self.options['global'])
+        self.addArguments(args, self.options['commit'])
+        args.append("-v")
+        if closeBranch:
+            args.append("--close-branch")
+        args.append("--message")
+        args.append(msg)
+        if isinstance(name, list):
+            dname, fnames = self.splitPathList(name)
+        else:
+            dname, fname = self.splitPath(name)
+        
+        # find the root of the repo
+        repodir = dname
+        while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+            repodir = os.path.dirname(repodir)
+            if repodir == os.sep:
+                return
+        
+        if isinstance(name, list):
+            self.addArguments(args, fnames)
+        else:
+            if dname != repodir or fname != ".":
+                args.append(fname)
+        
+        if noDialog:
+            self.startSynchronizedProcess(QProcess(), "hg", args, dname)
+        else:
+            dia = HgDialog(self.trUtf8('Commiting changes to Mercurial repository'))
+            res = dia.startProcess(args, dname)
+            if res:
+                dia.exec_()
+        self.emit(SIGNAL("committed()"))
+        self.checkVCSStatus()
+    
+    def vcsUpdate(self, name, noDialog = False, revision = None):
+        """
+        Public method used to update a file/directory with the Mercurial repository.
+        
+        @param name file/directory name to be updated (string or list of strings)
+        @param noDialog flag indicating quiet operations (boolean)
+        @keyparam revision revision to update to (string)
+        @return flag indicating, that the update contained an add
+            or delete (boolean)
+        """
+        args = []
+        args.append('update')
+        self.addArguments(args, self.options['global'])
+        self.addArguments(args, self.options['update'])
+        if revision is not None:
+            args.append("-r")
+            args.append(revision)
+        
+        if isinstance(name, list):
+            dname, fnames = self.splitPathList(name)
+        else:
+            dname, fname = self.splitPath(name)
+        
+        # find the root of the repo
+        repodir = dname
+        while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+            repodir = os.path.dirname(repodir)
+            if repodir == os.sep:
+                return False
+        
+        if noDialog:
+            self.startSynchronizedProcess(QProcess(), "hg", args, repodir)
+            res = False
+        else:
+            dia = HgDialog(self.trUtf8('Synchronizing with the Mercurial repository'))
+            res = dia.startProcess(args, repodir)
+            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 = []
+        args.append('add')
+        self.addArguments(args, self.options['global'])
+        self.addArguments(args, self.options['add'])
+        args.append("-v")
+        
+        if isinstance(name, list):
+            if isDir:
+                dname, fname = os.path.split(name[0])
+            else:
+                dname, fnames = self.splitPathList(name)
+        else:
+            if isDir:
+                dname, fname = os.path.split(name)
+            else:
+                dname, fname = self.splitPath(name)
+        
+        # find the root of the repo
+        repodir = dname
+        while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+            repodir = os.path.dirname(repodir)
+            if repodir == os.sep:
+                return
+        
+        if isinstance(name, list):
+            self.addArguments(args, name)
+        else:
+            args.append(name)
+        
+        if noDialog:
+            self.startSynchronizedProcess(QProcess(), "hg", args, repodir)
+        else:
+            dia = HgDialog(\
+                self.trUtf8('Adding files/directories to the Mercurial repository'))
+            res = dia.startProcess(args, repodir)
+            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 = []
+        args.append('remove')
+        self.addArguments(args, self.options['global'])
+        self.addArguments(args, self.options['remove'])
+        args.append("-v")
+        if noDialog and '--force' not in args:
+            args.append('--force')
+        
+        if isinstance(name, list):
+            dname, fnames = self.splitPathList(name)
+            self.addArguments(args, name)
+        else:
+            dname, fname = self.splitPath(name)
+            args.append(name)
+        
+        # find the root of the repo
+        repodir = dname
+        while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+            repodir = os.path.dirname(repodir)
+            if repodir == os.sep:
+                return False
+        
+        if noDialog:
+            res = self.startSynchronizedProcess(QProcess(), "hg", args, repodir)
+        else:
+            dia = HgDialog(\
+                self.trUtf8('Removing files/directories from the Mercurial repository'))
+            res = dia.startProcess(args, repodir)
+            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)
+        opts = self.options['global'][:]
+        force = '--force' in opts
+        if force:
+            opts.remove('--force')
+        
+        res = False
+        if noDialog:
+            if target is None:
+                return False
+            force = True
+            accepted = True
+        else:
+            dlg = HgCopyDialog(name, None, True, force)
+            accepted = dlg.exec_() == QDialog.Accepted
+            if accepted:
+                target, force = dlg.getData()
+        
+        if accepted:
+            args = []
+            args.append('rename')
+            self.addArguments(args, opts)
+            args.append("-v")
+            if force:
+                args.append('--force')
+            args.append(name)
+            args.append(target)
+            
+            dname, fname = self.splitPath(name)
+            # find the root of the repo
+            repodir = dname
+            while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+                repodir = os.path.dirname(repodir)
+                if repodir == os.sep:
+                    return False
+            
+            if noDialog:
+                res = self.startSynchronizedProcess(QProcess(), "hg", args, repodir)
+            else:
+                dia = HgDialog(self.trUtf8('Renaming {0}').format(name))
+                res = dia.startProcess(args, repodir)
+                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 vcsLog(self, name):
+        """
+        Public method used to view the log of a file/directory from the 
+        Mercurial repository.
+        
+        @param name file/directory name to show the log of (string)
+        """
+        self.log = HgLogDialog(self)
+        self.log.show()
+        self.log.start(name)
+    
+    def vcsDiff(self, name):
+        """
+        Public method used to view the difference of a file/directory to the 
+        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)
+        """
+        if isinstance(name, list):
+            names = name[:]
+        else:
+            names = [name]
+        for nam in names:
+            if os.path.isfile(nam):
+                editor = e5App().getObject("ViewManager").getOpenEditor(nam)
+                if editor and not editor.checkDirty() :
+                    return
+            else:
+                project = e5App().getObject("Project")
+                if nam == project.ppath and not project.saveAllScripts():
+                    return
+        self.diff = HgDiffDialog(self)
+        self.diff.show()
+        QApplication.processEvents()
+        self.diff.start(name)
+    
+    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)
+        """
+        self.status = HgStatusDialog(self)
+        self.status.show()
+        self.status.start(name)
+    
+    def vcsTag(self, name):
+        """
+        Public method used to set the tag in the Mercurial repository.
+        
+        @param name file/directory name to be tagged (string)
+        """
+        dname, fname = self.splitPath(name)
+        
+        # find the root of the repo
+        repodir = str(dname)
+        while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+            repodir = os.path.dirname(repodir)
+            if repodir == os.sep:
+                return
+        
+        dlg = HgTagDialog(self.tagsList)
+        if dlg.exec_() == QDialog.Accepted:
+            tag, tagOp = dlg.getParameters()
+            if tag in self.tagsList:
+                self.tagsList.remove(tag)
+            self.tagsList.insert(0, tag)
+        else:
+            return
+        
+        args = []
+        args.append('tag')
+        if tagOp == HgTagDialog.CreateLocalTag:
+            args.append('--local')
+        elif tagOp == HgTagDialog.DeleteTag:
+            args.append('--remove')
+        args.append('--message')
+        if tagOp != HgTagDialog.DeleteTag:
+            args.append("Created tag <{0}>.".format(tag))
+        else:
+            args.append("Removed tag <{0}>.".format(tag))
+        args.append(tag)
+        
+        dia = HgDialog(self.trUtf8('Taging in the Mercurial repository'))
+        res = dia.startProcess(args, repodir)
+        if res:
+            dia.exec_()
+    
+    def vcsRevert(self, name):
+        """
+        Public method used to revert changes made to a file/directory.
+        
+        @param name file/directory name to be reverted (string)
+        """
+        args = []
+        args.append('revert')
+        self.addArguments(args, self.options['global'])
+        args.append("--no-backup")
+        args.append("-v")
+        if isinstance(name, list):
+            dname, fnames = self.splitPathList(name)
+            self.addArguments(args, name)
+        else:
+            dname, fname = self.splitPath(name)
+            args.append(name)
+        
+        # find the root of the repo
+        repodir = dname
+        while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+            repodir = os.path.dirname(repodir)
+            if repodir == os.sep:
+                return
+        
+        dia = HgDialog(self.trUtf8('Reverting changes'))
+        res = dia.startProcess(args, repodir)
+        if res:
+            dia.exec_()
+        self.checkVCSStatus()
+    
+    def vcsMerge(self, name):
+        """
+        Public method used to merge a URL/revision into the local project.
+        
+        @param name file/directory name to be merged (string)
+        """
+        dname, fname = self.splitPath(name)
+        
+        opts = self.options['global'][:]
+        force = '--force' in opts
+        if force:
+            del opts[opts.index('--force')]
+        
+        dlg = HgMergeDialog(force, self.tagsList, self.branchesList)
+        if dlg.exec_() == QDialog.Accepted:
+            rev, force = dlg.getParameters()
+        else:
+            return
+        
+        # find the root of the repo
+        repodir = dname
+        while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+            repodir = os.path.dirname(repodir)
+            if repodir == os.sep:
+                return
+        
+        args = []
+        args.append('merge')
+        self.addArguments(args, opts)
+        if force:
+            args.append("--force")
+        if rev:
+            args.append("--rev")
+            args.append(rev)
+        
+        dia = HgDialog(self.trUtf8('Merging').format(name))
+        res = dia.startProcess(args, repodir)
+        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)
+        """
+        dlg = HgRevisionSelectionDialog(self.tagsList, self.branchesList)
+        if dlg.exec_() == QDialog.Accepted:
+            rev = dlg.getRevision()
+            self.vcsUpdate(name, revision = rev)
+
+    def vcsRegisteredState(self, name):
+        """
+        Public method used to get the registered state of a file in the vcs.
+        
+        @param name filename to check (string)
+        @return a combination of canBeCommited and canBeAdded
+        """
+        if name.endswith(os.sep):
+            name = name[:-1]
+        dname, fname = self.splitPath(name)
+        
+        if fname == '.' and os.path.isdir(os.path.join(dname, self.adminDir)):
+            return self.canBeCommitted
+        
+        # find the root of the repo
+        repodir = dname
+        while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+            repodir = os.path.dirname(repodir)
+            if repodir == os.sep:
+                return 0
+        
+        ioEncoding = Preferences.getSystem("IOEncoding")
+        process = QProcess()
+        args = []
+        args.append('status')
+        args.append('--all')
+        args.append('--noninteractive')
+        process.setWorkingDirectory(repodir)
+        process.start('hg', args)
+        procStarted = process.waitForStarted()
+        if procStarted:
+            finished = process.waitForFinished(30000)
+            if finished and process.exitCode() == 0:
+                output = \
+                    str(process.readAllStandardOutput(), ioEncoding, 'replace')
+                for line in output.splitlines():
+                    flag, path = line.split(" ", 1)
+                    absname = os.path.join(repodir, os.path.normcase(path))
+                    if flag not in "?I":
+                        if fname == '.':
+                            if absname.startswith(dname):
+                                return self.canBeCommitted
+                        else:
+                            if 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]
+        
+        found = False
+        for name in list(self.statusCache.keys()):
+            if os.path.dirname(name) == dname:
+                if shortcut:
+                    found = True
+                    break
+                if name in names:
+                    found = True
+                    names[name] = self.statusCache[name]
+        
+        if not found:
+            # find the root of the repo
+            repodir = dname
+            while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+                repodir = os.path.dirname(repodir)
+                if repodir == os.sep:
+                    return names
+        
+            ioEncoding = Preferences.getSystem("IOEncoding")
+            process = QProcess()
+            args = []
+            args.append('status')
+            args.append('--all')
+            args.append('--noninteractive')
+            process.setWorkingDirectory(dname)
+            process.start('hg', args)
+            procStarted = process.waitForStarted()
+            if procStarted:
+                finished = process.waitForFinished(30000)
+                if finished and process.exitCode() == 0:
+                    dirs = [x for x in names.keys() if os.path.isdir(x)]
+                    output = \
+                        str(process.readAllStandardOutput(), ioEncoding, 'replace')
+                    for line in output.splitlines():
+                        flag, path = line.split(" ", 1)
+                        name = os.path.join(repodir, os.path.normcase(path))
+                        if name.startswith(dname):
+                            if flag not in "?I":
+                                if name in names:
+                                    names[name] = self.canBeCommitted
+                                dirName = os.path.dirname(name)
+                                if dirName in names:
+                                    names[dirName] = self.canBeCommitted
+                                self.statusCache[name] = self.canBeCommitted
+                                self.statusCache[dirName] = self.canBeCommitted
+                                if dirs:
+                                    for d in dirs:
+                                        if name.startswith(d):
+                                            names[d] = self.canBeCommitted
+                                            dirs.remove(d)
+                                            break
+                            else:
+                                self.statusCache[name] = self.canBeAdded
+                                dirName = os.path.dirname(name)
+                                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 vcsCleanup(self, name):
+        """
+        Public method used to cleanup the working directory.
+        
+        @param name directory name to be cleaned up (string)
+        """
+        patterns = ['*.orig', '*.rej']
+        
+        entries = []
+        for pat in patterns:
+            entries.extend(Utilities.direntries(name, True, pat))
+        
+        for entry in entries:
+            try:
+                os.remove(entry)
+            except OSError:
+                pass
+    
+    def vcsCommandLine(self, name):
+        """
+        Public method used to execute arbitrary mercurial commands.
+        
+        @param name directory name of the working directory (string)
+        """
+        dlg = HgCommandDialog(self.commandHistory, name)
+        if dlg.exec_() == QDialog.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)
+            
+            # find the root of the repo
+            repodir = name
+            while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+                repodir = os.path.dirname(repodir)
+                if repodir == os.sep:
+                    return
+            
+            dia = HgDialog(self.trUtf8('Mercurial command'))
+            res = dia.startProcess(args, repodir)
+            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 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 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)
+        """
+        info = []
+        
+        process = QProcess()
+        args = []
+        args.append('parents')
+        args.append('--template')
+        args.append('{rev}:{node|short}@@@{tags}@@@{author|xmlescape}@@@'
+                    '{date|isodate}@@@{branches}\n')
+        process.setWorkingDirectory(ppath)
+        process.start('hg', args)
+        procStarted = process.waitForStarted()
+        if procStarted:
+            finished = process.waitForFinished(30000)
+            if finished and process.exitCode() == 0:
+                output = str(process.readAllStandardOutput(), 
+                    Preferences.getSystem("IOEncoding"), 'replace')
+                index = 0
+                for line in output.splitlines():
+                    index += 1
+                    changeset, tags, author, date, branches = line.split("@@@")
+                    cdate, ctime = date.split()[:2]
+                    info.append("""<p><table>""")
+                    info.append(QApplication.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(QApplication.translate("mercurial", 
+                            """<tr><td><b>Tags</b></td><td>{0}</td></tr>""")\
+                            .format('<br/>'.join(tags.split())))
+                    if branches:
+                        info.append(QApplication.translate("mercurial", 
+                            """<tr><td><b>Branches</b></td><td>{0}</td></tr>""")\
+                            .format('<br/>'.join(branches.split())))
+                    info.append(QApplication.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))
+                    info.append("""</table></p>""")
+        
+        url = ""
+        args =  []
+        args.append('showconfig')
+        args.append('paths.default')
+        process.setWorkingDirectory(ppath)
+        process.start('hg', args)
+        procStarted = process.waitForStarted()
+        if procStarted:
+            finished = process.waitForFinished(30000)
+            if finished and process.exitCode() == 0:
+                output = str(process.readAllStandardOutput(), 
+                    Preferences.getSystem("IOEncoding"), 'replace')
+                url = output.splitlines()[0].strip()
+        
+        return QApplication.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"""
+            """</table></p>\n"""
+            """{2}"""
+            ).format(self.versionStr, url, "\n".join(info))
+
+    ############################################################################
+    ## Private Subversion specific methods are below.
+    ############################################################################
+    
+    def __hgURL(self, url):
+        """
+        Private method to format a url for Mercurial.
+        
+        @param url unformatted url string (string)
+        @return properly formated url for subversion (string)
+        """
+        url = self.hgNormalizeURL(url)
+        url = url.split(':', 2)
+        if len(url) == 4:
+            scheme = url[0]
+            user = url[1]
+            host = url[2]
+            port, path = url[3].split("/",1)
+            return "%s:%s:%s:%s/%s" % (scheme, user, host, port, urllib.parse.quote(path))
+        elif len(url) == 3:
+            scheme = url[0]
+            host = url[1]
+            port, path = url[2].split("/",1)
+            return "%s:%s:%s/%s" % (scheme, host, port, urllib.parse.quote(path))
+        else:
+            scheme = url[0]
+            if scheme == "file":
+                return "%s:%s" % (scheme, urllib.parse.quote(url[1]))
+            else:
+                host, path = url[1][2:].split("/",1)
+                return "%s://%s/%s" % (scheme, host, urllib.parse.quote(path))
+
+    def hgNormalizeURL(self, url):
+        """
+        Public method to normalize a url for Mercurial.
+        
+        @param url url string (string)
+        @return properly normalized url for subversion (string)
+        """
+        url = url.replace('\\', '/')
+        if url.endswith('/'):
+            url = url[:-1]
+        urll = url.split('//')
+        return "%s//%s" % (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 successfull operation (boolean)
+        """
+        dlg = HgCopyDialog(name)
+        res = False
+        if dlg.exec_() == QDialog.Accepted:
+            target, force = dlg.getData()
+            
+            args = []
+            args.append('copy')
+            self.addArguments(args, self.options['global'])
+            args.append("-v")
+            args.append(name)
+            args.append(target)
+            
+            dname, fname = self.splitPath(name)
+            # find the root of the repo
+            repodir = dname
+            while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+                repodir = os.path.dirname(repodir)
+                if repodir == os.sep:
+                    return False
+            
+            dia = HgDialog(self.trUtf8('Copying {0}')
+                .format(name))
+            res = dia.startProcess(args, repodir)
+            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 hgListTagBranch(self, path, tags = True):
+        """
+        Public method used to list the available tags or branches.
+        
+        @param path directory name of the project (string)
+        @param tags flag indicating listing of branches or tags
+                (False = branches, True = tags)
+        """
+        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(path, tags, 
+                                     self.tagsList, allTagsBranchesList)
+        else:
+            if not self.showedBranches:
+                self.showedBranches = True
+                allTagsBranchesList = self.allTagsBranchesList
+            else:
+                self.branchesList = []
+                allTagsBranchesList = None
+            self.tagbranchList.start(path, 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)
+        """
+        self.annotate = HgAnnotateDialog(self)
+        self.annotate.show()
+        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)
+        """
+        if isinstance(name, list):
+            names = name[:]
+        else:
+            names = [name]
+        for nam in names:
+            if os.path.isfile(nam):
+                editor = e5App().getObject("ViewManager").getOpenEditor(nam)
+                if editor and not editor.checkDirty() :
+                    return
+            else:
+                project = e5App().getObject("Project")
+                if nam == project.ppath and not project.saveAllScripts():
+                    return
+        dlg = HgRevisionsSelectionDialog(self.tagsList, self.branchesList)
+        if dlg.exec_() == QDialog.Accepted:
+            revisions = dlg.getRevisions()
+            self.diff = HgDiffDialog(self)
+            self.diff.show()
+            self.diff.start(name, revisions)
+    
+    def hgLogLimited(self, name):
+        """
+        Public method used to view the (limited) log of a file/directory from the 
+        Mercurial repository.
+        
+        @param name file/directory name to show the log of (string)
+        """
+        noEntries, ok = QInputDialog.getInteger(\
+            None,
+            self.trUtf8("Mercurial Log"),
+            self.trUtf8("Select number of entries to show."),
+            self.getPlugin().getPreferences("LogLimit"), 1, 999999, 1)
+        if ok:
+            self.log = HgLogDialog(self)
+            self.log.show()
+            self.log.start(name, noEntries)
+    
+    def hgLogBrowser(self, path):
+        """
+        Public method used to browse the log of a file/directory from the 
+        Mercurial repository.
+        
+        @param path file/directory name to show the log of (string)
+        """
+        self.logBrowser = HgLogBrowserDialog(self)
+        self.logBrowser.show()
+        self.logBrowser.start(path)
+    
+    def hgIncoming(self, name):
+        """
+        Public method used to view the log of incoming changes from the 
+        Mercurial repository.
+        
+        @param name file/directory name to show the log of (string)
+        """
+        self.log = HgLogDialog(self, mode = "incoming")
+        self.log.show()
+        self.log.start(name)
+    
+    def hgOutgoing(self, name):
+        """
+        Public method used to view the log of outgoing changes from the 
+        Mercurial repository.
+        
+        @param name file/directory name to show the log of (string)
+        """
+        self.log = HgLogDialog(self, mode = "outgoing")
+        self.log.show()
+        self.log.start(name)
+    
+    def hgPull(self, name):
+        """
+        Public method used to pull changes from a remote Mercurial repository.
+        
+        @param name directory name of the project to be pulled to (string)
+        """
+        args = []
+        args.append('pull')
+        self.addArguments(args, self.options['global'])
+        args.append('-v')
+        
+        # find the root of the repo
+        repodir = self.splitPath(name)[0]
+        while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+            repodir = os.path.dirname(repodir)
+            if repodir == os.sep:
+                return
+        
+        dia = HgDialog(self.trUtf8('Pulling from a remote Mercurial repository'))
+        res = dia.startProcess(args, repodir)
+        if res:
+            dia.exec_()
+            res = dia.hasAddOrDelete()
+        self.checkVCSStatus()
+    
+    def hgPush(self, name):
+        """
+        Public method used to push changes to a remote Mercurial repository.
+        
+        @param name directory name of the project to be pushed from (string)
+        """
+        args = []
+        args.append('push')
+        self.addArguments(args, self.options['global'])
+        args.append('-v')
+        
+        # find the root of the repo
+        repodir = self.splitPath(name)[0]
+        while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+            repodir = os.path.dirname(repodir)
+            if repodir == os.sep:
+                return
+        
+        dia = HgDialog(self.trUtf8('Pushing to a remote Mercurial repository'))
+        res = dia.startProcess(args, repodir)
+        if res:
+            dia.exec_()
+            res = dia.hasAddOrDelete()
+        self.checkVCSStatus()
+    
+    def hgInfo(self, ppath, mode = "heads"):
+        """
+        Public method to show information about the heads of the repository.
+        
+        @param ppath local path to get the repository infos (string)
+        @keyparam mode mode of the operation (string, one of heads, parents, tip)
+        """
+        if mode not in ("heads", "parents", "tip"):
+            mode = "heads"
+        
+        info = []
+        
+        # find the root of the repo
+        repodir = self.splitPath(ppath)[0]
+        while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+            repodir = os.path.dirname(repodir)
+            if repodir == os.sep:
+                return
+        
+        process = QProcess()
+        args = []
+        args.append(mode)
+        args.append('--template')
+        args.append('{rev}:{node|short}@@@{tags}@@@{author|xmlescape}@@@'
+                    '{date|isodate}@@@{branches}@@@{parents}\n')
+        
+        process.setWorkingDirectory(repodir)
+        process.start('hg', args)
+        procStarted = process.waitForStarted()
+        if procStarted:
+            finished = process.waitForFinished(30000)
+            if finished and process.exitCode() == 0:
+                output = str(process.readAllStandardOutput(), 
+                    Preferences.getSystem("IOEncoding"), 'replace')
+                index = 0
+                for line in output.splitlines():
+                    index += 1
+                    changeset, tags, author, date, branches, parents = line.split("@@@")
+                    cdate, ctime = date.split()[:2]
+                    info.append("""<p><table>""")
+                    if mode == "heads":
+                        info.append(QApplication.translate("mercurial", 
+                            """<tr><td><b>Head #{0}</b></td><td></td></tr>\n"""
+                            .format(index, changeset)))
+                    elif mode == "parents":
+                        info.append(QApplication.translate("mercurial", 
+                            """<tr><td><b>Parent #{0}</b></td><td></td></tr>\n"""
+                            .format(index, changeset)))
+                    elif mode == "tip":
+                        info.append(QApplication.translate("mercurial", 
+                            """<tr><td><b>Tip</b></td><td></td></tr>\n"""))
+                    info.append(QApplication.translate("mercurial", 
+                        """<tr><td><b>Changeset</b></td><td>{0}</td></tr>""")\
+                        .format(changeset))
+                    if tags:
+                        info.append(QApplication.translate("mercurial", 
+                            """<tr><td><b>Tags</b></td><td>{0}</td></tr>""")\
+                            .format('<br/>'.join(tags.split())))
+                    if branches:
+                        info.append(QApplication.translate("mercurial", 
+                            """<tr><td><b>Branches</b></td><td>{0}</td></tr>""")\
+                            .format('<br/>'.join(branches.split())))
+                    if parents:
+                        info.append(QApplication.translate("mercurial", 
+                            """<tr><td><b>Parents</b></td><td>{0}</td></tr>""")\
+                            .format('<br/>'.join(parents.split())))
+                    info.append(QApplication.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 hgResolve(self, name):
+        """
+        Public method used to resolve conflicts of a file/directory.
+        
+        @param name file/directory name to be resolved (string)
+        """
+        args = []
+        args.append('resolve')
+        self.addArguments(args, self.options['global'])
+        args.append("--mark")
+        
+        if isinstance(name, list):
+            dname, fnames = self.splitPathList(name)
+            self.addArguments(args, name)
+        else:
+            dname, fname = self.splitPath(name)
+            args.append(name)
+        
+        # find the root of the repo
+        repodir = dname
+        while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+            repodir = os.path.dirname(repodir)
+            if repodir == os.sep:
+                return False
+        
+        dia = HgDialog(
+            self.trUtf8('Resolving files/directories'))
+        res = dia.startProcess(args, repodir)
+        if res:
+            dia.exec_()
+        self.checkVCSStatus()
+    
+    def hgBranch(self, name):
+        """
+        Public method used to set the tag in the Mercurial repository.
+        
+        @param name file/directory name to be tagged (string)
+        """
+        dname, fname = self.splitPath(name)
+        
+        # find the root of the repo
+        repodir = str(dname)
+        while not os.path.isdir(os.path.join(repodir, self.adminDir)):
+            repodir = os.path.dirname(repodir)
+            if repodir == os.sep:
+                return
+        
+        name, ok = QInputDialog.getItem(
+            None,
+            self.trUtf8("Create Branch"),
+            self.trUtf8("Enter branch name"),
+            self.branchesList,
+            0, True)
+        if ok and name:
+            args = []
+            args.append('branch')
+            args.append(name)
+            
+            dia = HgDialog(self.trUtf8('Creating branch in the Mercurial repository'))
+            res = dia.startProcess(args, repodir)
+            if res:
+                dia.exec_()
+    
+    ############################################################################
+    ## Methods to get the helper objects are below.
+    ############################################################################
+    
+    def vcsGetProjectBrowserHelper(self, browser, project, isTranslationsBrowser = False):
+        """
+        Public method to instanciate a helper object for the different project browsers.
+        
+        @param browser reference to the project browser object
+        @param project reference to the project object
+        @param isTranslationsBrowser flag indicating, the helper is requested for the
+            translations browser (this needs some special treatment)
+        @return the project browser helper object
+        """
+        return HgProjectBrowserHelper(self, browser, project, isTranslationsBrowser)
+        
+    def vcsGetProjectHelper(self, project):
+        """
+        Public method to instanciate a helper object for the project.
+        
+        @param project reference to the project object
+        @return the project helper object
+        """
+        helper = self.__plugin.getProjectHelper()
+        helper.setObjects(self, project)
+        return helper
+
+    ############################################################################
+    ##  Status Monitor Thread methods
+    ############################################################################
+
+    def _createStatusMonitorThread(self, interval, project):
+        """
+        Protected method to create an instance of the VCS status monitor thread.
+        
+        @param project reference to the project object
+        @param interval check interval for the monitor thread in seconds (integer)
+        @return reference to the monitor thread (QThread)
+        """
+        return HgStatusMonitorThread(interval, project.ppath, self)

eric ide

mercurial