Started implementing an interface to the Mercurial command server (as of Mercurial >= 1.9).

Sun, 28 Aug 2011 18:53:13 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 28 Aug 2011 18:53:13 +0200
changeset 1240
4d5fc346bd3b
parent 1239
697757468865
child 1241
09c6155ee612

Started implementing an interface to the Mercurial command server (as of Mercurial >= 1.9).

Plugins/VcsPlugins/vcsMercurial/HgClient.py file | annotate | diff | comparison | revisions
Plugins/VcsPlugins/vcsMercurial/HgDialog.py file | annotate | diff | comparison | revisions
Plugins/VcsPlugins/vcsMercurial/hg.py file | annotate | diff | comparison | revisions
eric5.e4p file | annotate | diff | comparison | revisions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/VcsPlugins/vcsMercurial/HgClient.py	Sun Aug 28 18:53:13 2011 +0200
@@ -0,0 +1,260 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2011 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing an interface to the Mercurial command server.
+"""
+
+import struct
+import io
+
+from PyQt4.QtCore import QProcess, QProcessEnvironment, QObject, QByteArray, \
+    QCoreApplication
+
+import Preferences
+
+
+class HgClient(QObject):
+    """
+    Class implementing the Mercurial command server interface.
+    """
+    InputFormat = ">I"
+    OutputFormat = ">cI"
+    OutputFormatSize = struct.calcsize(OutputFormat)
+    ReturnFormat = ">i"
+    
+    def __init__(self, repoPath, encoding, parent=None):
+        """
+        Constructor
+        
+        @param repoPath root directory of the repository (string)
+        @param encoding encoding to be used by the command server (string)
+        @param parent reference to the parent object (QObject)
+        """
+        super().__init__(parent)
+        
+        self.__server = QProcess()
+        self.__started = False
+        self.__version = None
+        self.__encoding = Preferences.getSystem("IOEncoding")
+        
+        # connect signals
+        self.__server.finished.connect(self.__serverFinished)
+        
+        # generate command line and environment
+        self.__serverArgs = []
+        self.__serverArgs.append("serve")
+        self.__serverArgs.append("--cmdserver")
+        self.__serverArgs.append("pipe")
+        self.__serverArgs.append("--config")
+        self.__serverArgs.append("ui.interactive=True")
+        if repoPath:
+            self.__serverArgs.append("--repository")
+            self.__serverArgs.append(repoPath)
+        
+        if encoding:
+            env = QProcessEnvironment.systemEnvironment()
+            env.insert("HGENCODING", encoding)
+            self.__server.setProcessEnvironment(env)
+            self.__encoding = encoding
+    
+    def startServer(self):
+        """
+        Public method to start the command server.
+        
+        @return tuple of flag indicating a successful start (boolean) and
+            an error message (string) in case of failure
+        """
+        self.__server.start('hg', self.__serverArgs)
+        serverStarted = self.__server.waitForStarted()
+        if not serverStarted:
+            return False, self.trUtf8(
+                    'The process {0} could not be started. '
+                    'Ensure, that it is in the search path.'
+                ).format('hg')
+        
+        self.__server.setReadChannel(QProcess.StandardOutput)
+        ok, error = self.__readHello()
+        self.__started = ok
+        return ok, error
+    
+    def stopServer(self):
+        """
+        Public method to stop the command server.
+        """
+        self.__server.closeWriteChannel()
+        self.__server.waitForFinished()
+    
+    def restartServer(self):
+        """
+        Public method to restart the command server.
+        
+        @return tuple of flag indicating a successful start (boolean) and
+            an error message (string) in case of failure
+        """
+        self.stopServer()
+        return self.startServer()
+    
+    def __readHello(self):
+        """
+        Private method to read the hello message sent by the command server.
+        
+        @return tuple of flag indicating success (boolean) and an error message
+            in case of failure (string)
+        """
+        ch, msg = self.__readChannel()
+        if not ch:
+            return False, self.trUtf8("Did not receive the 'hello' message.")
+        elif ch != "o":
+            return False, self.trUtf8("Received data on unexpected channel.")
+        
+        msg = msg.split("\n")
+        
+        if not msg[0].startswith("capabilities: "):
+            return False, self.trUtf8("Bad 'hello' message, expected 'capabilities: '"
+                                      " but got '{0}'.").format(msg[0])
+        self.__capabilities = msg[0][len('capabilities: '):]
+        if not self.__capabilities:
+            return False, self.trUtf8("'capabilities' message did not contain"
+                                      " any capability.")
+        
+        self.__capabilities = set(self.__capabilities.split())
+        if "runcommand" not in self.__capabilities:
+            return False, "'capabilities' did not contain 'runcommand'."
+        
+        if not msg[1].startswith("encoding: "):
+            return False, self.trUtf8("Bad 'hello' message, expected 'encoding: '"
+                                      " but got '{0}'.").format(msg[1])
+        encoding = msg[1][len('encoding: '):]
+        if not encoding:
+            return False, self.trUtf8("'encoding' message did not contain"
+                                      " any encoding.")
+        self.__encoding = encoding
+        
+        return True, ""
+    
+    def __serverFinished(self, exitCode, exitStatus):
+        """
+        Private slot connected to the finished signal.
+        
+        @param exitCode exit code of the process (integer)
+        @param exitStatus exit status of the process (QProcess.ExitStatus)
+        """
+        self.__started = False
+    
+    def __readChannel(self):
+        """
+        Private method to read data from the command server.
+        
+        @return tuple of channel designator and channel data (string, integer or string)
+        """
+        if self.__server.bytesAvailable() > 0 or \
+           self.__server.waitForReadyRead(10000):
+            data = bytes(self.__server.read(HgClient.OutputFormatSize))
+            if not data:
+                return "", ""
+            
+            channel, length = struct.unpack(HgClient.OutputFormat, data)
+            channel = channel.decode(self.__encoding)
+            if channel in "IL":
+                return channel, length
+            else:
+                return (channel,
+                        str(self.__server.read(length), self.__encoding, "replace"))
+        else:
+            return "", ""
+    
+    def __writeDataBlock(self, data):
+        """
+        Private slot to write some data to the command server.
+        
+        @param data data to be sent (string)
+        """
+        if not isinstance(data, bytes):
+            data = data.encode(self.__encoding)
+        self.__server.write(QByteArray(struct.pack(HgClient.InputFormat, len(data))))
+        self.__server.write(QByteArray(data))
+        self.__server.waitForBytesWritten()
+    
+    def __runcommand(self, args, inputChannels, outputChannels):
+        """
+        Private method to run a command in the server (low level).
+        
+        @param args list of arguments for the command (list of string)
+        @param inputChannels dictionary of input channels. The dictionary must
+            have the keys 'I' and 'L' and each entry must be a function receiving
+            the number of bytes to write.
+        @param outputChannels dictionary of output channels. The dictionary must
+            have the keys 'o' and 'e' and each entry must be a function receiving
+            the data.
+        """
+        if not self.__started:
+            return -1
+        
+        self.__server.write(QByteArray(b'runcommand\n'))
+        self.__writeDataBlock('\0'.join(args))
+        
+        while True:
+            QCoreApplication.processEvents()
+            if self.__server.bytesAvailable() == 0:
+                continue
+            channel, data = self.__readChannel()
+            
+            # input channels
+            if channel in inputChannels:
+                self.__writeDataBlock(inputChannels[channel](data))
+            
+            # output channels
+            elif channel in outputChannels:
+                outputChannels[channel](data)
+            
+            # result channel, command is finished
+            elif channel == "r":
+                return struct.unpack(HgClient.ReturnFormat, 
+                                     data.encode(self.__encoding))[0]
+            
+            # unexpected but required channel
+            elif channel.isupper():
+                raise RuntimeError(
+                    "Unexpected but required channel '{0}'.".format(channel))
+            
+            # optional channels
+            else:
+                pass
+    
+    def runcommand(self, args, prompt=None, input=None):
+        """
+        Public method to execute a command via the command server.
+        
+        @param args list of arguments for the command (list of string)
+        @param prompt function to reply to prompts by the server. It
+            receives the max number of bytes to return and the contents
+            of the output channel received so far.
+        @param input function to reply to bulk data requests by the server.
+            It receives the max number of bytes to return.
+        @return output and errors of the command server (string)
+        """
+        output = io.StringIO()
+        error = io.StringIO()
+        outputChannels = {
+            "o": output.write,
+            "e": error.write
+        }
+        
+        inputChannels = {}
+        if prompt is not None:
+            def func(size):
+                reply = prompt(size, output.getvalue())
+                return reply
+            inputChannels["L"] = func
+        if input is not None:
+            inputChannels["I"] = input
+        
+        self.__runcommand(args, inputChannels, outputChannels)
+        out = output.getvalue()
+        err = error.getvalue()
+        
+        return out, err
+        
--- a/Plugins/VcsPlugins/vcsMercurial/HgDialog.py	Sat Aug 27 11:50:54 2011 +0200
+++ b/Plugins/VcsPlugins/vcsMercurial/HgDialog.py	Sun Aug 28 18:53:13 2011 +0200
@@ -27,11 +27,12 @@
     shows the output of the process. The dialog is modal,
     which causes a synchronized execution of the process.
     """
-    def __init__(self, text, parent=None):
+    def __init__(self, text, hg=None, parent=None):
         """
         Constructor
         
         @param text text to be shown by the label (string)
+        @param hg reference to the Mercurial interface object (Hg)
         @param parent parent widget (QWidget)
         """
         super().__init__(parent)
@@ -43,6 +44,7 @@
         self.proc = None
         self.username = ''
         self.password = ''
+        self.__hgClient = hg.getClient()
         
         self.outputGroup.setTitle(text)
     
@@ -114,33 +116,47 @@
         else:
             self.__updateCommand = False
         
-        self.proc = QProcess()
-        
         if showArgs:
             self.resultbox.append(' '.join(args))
             self.resultbox.append('')
         
-        self.proc.finished.connect(self.__procFinished)
-        self.proc.readyReadStandardOutput.connect(self.__readStdout)
-        self.proc.readyReadStandardError.connect(self.__readStderr)
-        
-        if workingDir:
-            self.proc.setWorkingDirectory(workingDir)
-        self.proc.start('hg', args)
-        procStarted = self.proc.waitForStarted()
-        if not procStarted:
-            self.buttonBox.setFocus()
-            self.inputGroup.setEnabled(False)
-            E5MessageBox.critical(self,
-                self.trUtf8('Process Generation Error'),
-                self.trUtf8(
-                    'The process {0} could not be started. '
-                    'Ensure, that it is in the search path.'
-                ).format('hg'))
+        if self.__hgClient is None:
+            self.proc = QProcess()
+            
+            self.proc.finished.connect(self.__procFinished)
+            self.proc.readyReadStandardOutput.connect(self.__readStdout)
+            self.proc.readyReadStandardError.connect(self.__readStderr)
+            
+            if workingDir:
+                self.proc.setWorkingDirectory(workingDir)
+            self.proc.start('hg', args)
+            procStarted = self.proc.waitForStarted()
+            if not procStarted:
+                self.buttonBox.setFocus()
+                self.inputGroup.setEnabled(False)
+                E5MessageBox.critical(self,
+                    self.trUtf8('Process Generation Error'),
+                    self.trUtf8(
+                        'The process {0} could not be started. '
+                        'Ensure, that it is in the search path.'
+                    ).format('hg'))
+            else:
+                self.inputGroup.setEnabled(True)
+                self.inputGroup.show()
+            return procStarted
         else:
-            self.inputGroup.setEnabled(True)
-            self.inputGroup.show()
-        return procStarted
+            out, err = self.__hgClient.runcommand(args)
+            
+            if out:
+                self.__showOutput(out)
+            if err:
+                self.__showError(err)
+            
+            self.normal = True
+            
+            self.__finish()
+            
+            return True
     
     def normalExit(self):
         """
@@ -170,15 +186,23 @@
             s = str(self.proc.readAllStandardOutput(),
                     Preferences.getSystem("IOEncoding"),
                     'replace')
-            self.resultbox.insertPlainText(s)
-            self.resultbox.ensureCursorVisible()
-            
-            # check for a changed project file
-            if self.__updateCommand:
-                for line in s.splitlines():
-                    if '.e4p' in line:
-                        self.__hasAddOrDelete = True
-                        break
+            self.__showOutput(s)
+    
+    def __showOutput(self, out):
+        """
+        Private slot to show some output.
+        
+        @param out output to be shown (string)
+        """
+        self.resultbox.insertPlainText(out)
+        self.resultbox.ensureCursorVisible()
+        
+        # check for a changed project file
+        if self.__updateCommand:
+            for line in out.splitlines():
+                if '.e4p' in line:
+                    self.__hasAddOrDelete = True
+                    break
         
         QCoreApplication.processEvents()
     
@@ -190,12 +214,20 @@
         error pane.
         """
         if self.proc is not None:
-            self.errorGroup.show()
             s = str(self.proc.readAllStandardError(),
                     Preferences.getSystem("IOEncoding"),
                     'replace')
-            self.errors.insertPlainText(s)
-            self.errors.ensureCursorVisible()
+            self.__showError(s)
+    
+    def __showError(self, out):
+        """
+        Private slot to show some error.
+        
+        @param out error to be shown (string)
+        """
+        self.errorGroup.show()
+        self.errors.insertPlainText(out)
+        self.errors.ensureCursorVisible()
         
         QCoreApplication.processEvents()
     
--- a/Plugins/VcsPlugins/vcsMercurial/hg.py	Sat Aug 27 11:50:54 2011 +0200
+++ b/Plugins/VcsPlugins/vcsMercurial/hg.py	Sun Aug 28 18:53:13 2011 +0200
@@ -45,6 +45,7 @@
 from .HgBackoutDialog import HgBackoutDialog
 from .HgServeDialog import HgServeDialog
 from .HgUtilities import getConfigPath
+from .HgClient import HgClient
 
 from .BookmarksExtension.bookmarks import Bookmarks
 from .QueuesExtension.queues import Queues
@@ -142,6 +143,8 @@
         self.__iniWatcher.fileChanged.connect(self.__iniFileChanged)
         self.__iniWatcher.addPath(getConfigPath())
         
+        self.__client = None
+        
         # instantiate the extensions
         self.__extensions = {
             "bookmarks": Bookmarks(self),
@@ -189,6 +192,17 @@
         # shut down the extensions
         for extension in self.__extensions.values():
             extension.shutdown()
+        
+        # shut down the client
+        self.__client and self.__client.stopServer()
+    
+    def getClient(self):
+        """
+        Public method to get a reference to the command server interface.
+        
+        @return reference to the client (HgClient)
+        """
+        return self.__client
     
     def vcsExists(self):
         """
@@ -272,6 +286,7 @@
         args = []
         args.append('init')
         args.append(projectDir)
+        # init is not possible with the command server
         dia = HgDialog(self.trUtf8('Creating Mercurial repository'))
         res = dia.startProcess(args)
         if res:
@@ -287,7 +302,8 @@
                 args.append('--addremove')
                 args.append('--message')
                 args.append(msg)
-                dia = HgDialog(self.trUtf8('Initial commit to Mercurial repository'))
+                dia = HgDialog(self.trUtf8('Initial commit to Mercurial repository'),
+                               self)
                 res = dia.startProcess(args, projectDir)
                 if res:
                     dia.exec_()
@@ -326,9 +342,14 @@
         args.append(projectDir)
         
         if noDialog:
-            return self.startSynchronizedProcess(QProcess(), 'hg', args)
+            if self.__client is None:
+                return self.startSynchronizedProcess(QProcess(), 'hg', args)
+            else:
+                out, err = self.__client.runcommand(args)
+                return err == ""
         else:
-            dia = HgDialog(self.trUtf8('Cloning project from a Mercurial repository'))
+            dia = HgDialog(self.trUtf8('Cloning project from a Mercurial repository'),
+                           self)
             res = dia.startProcess(args)
             if res:
                 dia.exec_()
@@ -424,7 +445,8 @@
         if noDialog:
             self.startSynchronizedProcess(QProcess(), "hg", args, dname)
         else:
-            dia = HgDialog(self.trUtf8('Commiting changes to Mercurial repository'))
+            dia = HgDialog(self.trUtf8('Committing changes to Mercurial repository'),
+                           self)
             res = dia.startProcess(args, dname)
             if res:
                 dia.exec_()
@@ -469,10 +491,14 @@
                 return False
         
         if noDialog:
-            self.startSynchronizedProcess(QProcess(), "hg", args, repodir)
+            if self.__client is None:
+                self.startSynchronizedProcess(QProcess(), 'hg', args, repodir)
+            else:
+                out, err = self.__client.runcommand(args)
             res = False
         else:
-            dia = HgDialog(self.trUtf8('Synchronizing with the Mercurial repository'))
+            dia = HgDialog(self.trUtf8('Synchronizing with the Mercurial repository'),
+                           self)
             res = dia.startProcess(args, repodir)
             if res:
                 dia.exec_()
@@ -518,10 +544,13 @@
             args.append(name)
         
         if noDialog:
-            self.startSynchronizedProcess(QProcess(), "hg", args, repodir)
+            if self.__client is None:
+                self.startSynchronizedProcess(QProcess(), 'hg', args, repodir)
+            else:
+                out, err = self.__client.runcommand(args)
         else:
             dia = HgDialog(
-                self.trUtf8('Adding files/directories to the Mercurial repository'))
+                self.trUtf8('Adding files/directories to the Mercurial repository'), self)
             res = dia.startProcess(args, repodir)
             if res:
                 dia.exec_()
@@ -578,10 +607,15 @@
                 return False
         
         if noDialog:
-            res = self.startSynchronizedProcess(QProcess(), "hg", args, repodir)
+            if self.__client is None:
+                res = self.startSynchronizedProcess(QProcess(), 'hg', args, repodir)
+            else:
+                out, err = self.__client.runcommand(args)
+                res = err == ""
         else:
             dia = HgDialog(
-                self.trUtf8('Removing files/directories from the Mercurial repository'))
+                self.trUtf8('Removing files/directories from the Mercurial repository'),
+                self)
             res = dia.startProcess(args, repodir)
             if res:
                 dia.exec_()
@@ -637,8 +671,13 @@
             
             if noDialog:
                 res = self.startSynchronizedProcess(QProcess(), "hg", args, repodir)
+                if self.__client is None:
+                    res = self.startSynchronizedProcess(QProcess(), 'hg', args, repodir)
+                else:
+                    out, err = self.__client.runcommand(args)
+                    res = err == ""
             else:
-                dia = HgDialog(self.trUtf8('Renaming {0}').format(name))
+                dia = HgDialog(self.trUtf8('Renaming {0}').format(name), self)
                 res = dia.startProcess(args, repodir)
                 if res:
                     dia.exec_()
@@ -743,7 +782,7 @@
             args.append("Removed tag <{0}>.".format(tag))
         args.append(tag)
         
-        dia = HgDialog(self.trUtf8('Taging in the Mercurial repository'))
+        dia = HgDialog(self.trUtf8('Taging in the Mercurial repository'), self)
         res = dia.startProcess(args, repodir)
         if res:
             dia.exec_()
@@ -773,7 +812,7 @@
             if repodir == os.sep:
                 return
         
-        dia = HgDialog(self.trUtf8('Reverting changes'))
+        dia = HgDialog(self.trUtf8('Reverting changes'), self)
         res = dia.startProcess(args, repodir)
         if res:
             dia.exec_()
@@ -821,7 +860,7 @@
             args.append("--rev")
             args.append(rev)
         
-        dia = HgDialog(self.trUtf8('Merging').format(name))
+        dia = HgDialog(self.trUtf8('Merging').format(name), self)
         res = dia.startProcess(args, repodir)
         if res:
             dia.exec_()
@@ -883,32 +922,40 @@
             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 + os.path.sep):
-                                return self.canBeCommitted
-                            if absname == dname:
-                                return self.canBeCommitted
-                        else:
-                            if absname == name:
-                                return self.canBeCommitted
+        
+        output = ""
+        if self.__client is None:
+            process = QProcess()
+            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')
+        else:
+            output, error = self.__client.runcommand(args)
+        
+        if output:
+            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 + os.path.sep):
+                            return self.canBeCommitted
+                        if absname == dname:
+                            return self.canBeCommitted
+                    else:
+                        if absname == name:
+                            return self.canBeCommitted
         
         return self.canBeAdded
     
@@ -944,44 +991,52 @@
                 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.normcase(os.path.join(repodir, path))
-                        dirName = os.path.dirname(name)
-                        if name.startswith(dname):
-                            if 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
+            
+            output = ""
+            if self.__client is None:
+                process = QProcess()
+                process.setWorkingDirectory(dname)
+                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')
+            else:
+                output, error = self.__client.runcommand(args)
+            
+            if output:
+                dirs = [x for x in names.keys() if os.path.isdir(x)]
+                for line in output.splitlines():
+                    flag, path = line.split(" ", 1)
+                    name = os.path.normcase(os.path.join(repodir, path))
+                    dirName = os.path.dirname(name)
+                    if name.startswith(dname):
                         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
+                            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
     
@@ -1044,7 +1099,7 @@
                 if repodir == os.sep:
                     return
             
-            dia = HgDialog(self.trUtf8('Mercurial command'))
+            dia = HgDialog(self.trUtf8('Mercurial command'), self)
             res = dia.startProcess(args, repodir)
             if res:
                 dia.exec_()
@@ -1077,65 +1132,78 @@
         """
         info = []
         
-        process = QProcess()
         args = []
         args.append('parents')
         args.append('--template')
         args.append('{rev}:{node|short}@@@{tags}@@@{author|xmlescape}@@@'
                     '{date|isodate}@@@{branches}@@@{bookmarks}\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, bookmarks = line.split("@@@")
-                    cdate, ctime = date.split()[:2]
-                    info.append("""<p><table>""")
+        
+        output = ""
+        if self.__client is None:
+            process = QProcess()
+            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')
+        else:
+            output, error = self.__client.runcommand(args)
+        
+        if output:
+            index = 0
+            for line in output.splitlines():
+                index += 1
+                changeset, tags, author, date, branches, bookmarks = 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>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 bookmarks:
-                        info.append(QApplication.translate("mercurial",
-                            """<tr><td><b>Bookmarks</b></td><td>{0}</td></tr>""")\
-                            .format('<br/>'.join(bookmarks.split())))
-                    if branches:
-                        info.append(QApplication.translate("mercurial",
-                            """<tr><td><b>Branches</b></td><td>{0}</td></tr>""")\
-                            .format('<br/>'.join(branches.split())))
+                        """<tr><td><b>Tags</b></td><td>{0}</td></tr>""")\
+                        .format('<br/>'.join(tags.split())))
+                if bookmarks:
+                    info.append(QApplication.translate("mercurial",
+                        """<tr><td><b>Bookmarks</b></td><td>{0}</td></tr>""")\
+                        .format('<br/>'.join(bookmarks.split())))
+                if branches:
                     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>""")
+                        """<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')
-                if output:
-                    url = output.splitlines()[0].strip()
-                else:
-                    url = ""
+        
+        output = ""
+        if self.__client is None:
+            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')
+        else:
+            output, error = self.__client.runcommand(args)
+        
+        if output:
+            url = output.splitlines()[0].strip()
+        else:
+            url = ""
         
         return QApplication.translate('mercurial',
             """<h3>Repository information</h3>\n"""
@@ -1222,7 +1290,7 @@
                     return False
             
             dia = HgDialog(self.trUtf8('Copying {0}')
-                .format(name))
+                .format(name), self)
             res = dia.startProcess(args, repodir)
             if res:
                 dia.exec_()
@@ -1242,30 +1310,38 @@
         @param repodir directory name of the repository (string)
         @return list of tags (list of string)
         """
-        ioEncoding = Preferences.getSystem("IOEncoding")
-        process = QProcess()
         args = []
         args.append('tags')
         args.append('--verbose')
-        process.setWorkingDirectory(repodir)
-        process.start('hg', args)
-        procStarted = process.waitForStarted()
-        if procStarted:
-            finished = process.waitForFinished(30000)
-            if finished and process.exitCode() == 0:
-                self.tagsList = []
-                output = \
-                    str(process.readAllStandardOutput(), ioEncoding, 'replace')
-                for line in output.splitlines():
-                    l = line.strip().split()
-                    if l[-1][0] in "1234567890":
-                        # last element is a rev:changeset
-                        del l[-1]
-                    else:
-                        del l[-2:]
-                    name = " ".join(l)
-                    if name not in ["tip", "default"]:
-                        self.tagsList.append(name)
+        
+        output = ""
+        if self.__client is None:
+            process = QProcess()
+            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')
+        else:
+            output, error = self.__client.runcommand(args)
+        
+        if output:
+            self.tagsList = []
+            for line in output.splitlines():
+                l = line.strip().split()
+                if l[-1][0] in "1234567890":
+                    # last element is a rev:changeset
+                    del l[-1]
+                else:
+                    del l[-2:]
+                name = " ".join(l)
+                if name not in ["tip", "default"]:
+                    self.tagsList.append(name)
         
         return self.tagsList[:]
     
@@ -1276,30 +1352,38 @@
         @param repodir directory name of the repository (string)
         @return list of branches (list of string)
         """
-        ioEncoding = Preferences.getSystem("IOEncoding")
-        process = QProcess()
         args = []
         args.append('branches')
         args.append('--closed')
-        process.setWorkingDirectory(repodir)
-        process.start('hg', args)
-        procStarted = process.waitForStarted()
-        if procStarted:
-            finished = process.waitForFinished(30000)
-            if finished and process.exitCode() == 0:
-                self.branchesList = []
-                output = \
-                    str(process.readAllStandardOutput(), ioEncoding, 'replace')
-                for line in output.splitlines():
-                    l = line.strip().split()
-                    if l[-1][0] in "1234567890":
-                        # last element is a rev:changeset
-                        del l[-1]
-                    else:
-                        del l[-2:]
-                    name = " ".join(l)
-                    if name not in ["tip", "default"]:
-                        self.branchesList.append(name)
+        
+        output = ""
+        if self.__client is None:
+            process = QProcess()
+            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')
+        else:
+            output, error = self.__client.runcommand(args)
+        
+        if output:
+            self.branchesList = []
+            for line in output.splitlines():
+                l = line.strip().split()
+                if l[-1][0] in "1234567890":
+                    # last element is a rev:changeset
+                    del l[-1]
+                else:
+                    del l[-2:]
+                name = " ".join(l)
+                if name not in ["tip", "default"]:
+                    self.branchesList.append(name)
         
         return self.branchesList[:]
     
@@ -1474,7 +1558,7 @@
             if repodir == os.sep:
                 return
         
-        dia = HgDialog(self.trUtf8('Pulling from a remote Mercurial repository'))
+        dia = HgDialog(self.trUtf8('Pulling from a remote Mercurial repository'), self)
         res = dia.startProcess(args, repodir)
         if res:
             dia.exec_()
@@ -1506,7 +1590,7 @@
             if repodir == os.sep:
                 return
         
-        dia = HgDialog(self.trUtf8('Pushing to a remote Mercurial repository'))
+        dia = HgDialog(self.trUtf8('Pushing to a remote Mercurial repository'), self)
         res = dia.startProcess(args, repodir)
         if res:
             dia.exec_()
@@ -1525,75 +1609,80 @@
         
         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}@@@{bookmarks}\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, bookmarks = \
-                        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"""))
+        output = ""
+        if self.__client is None:
+            # 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()
+            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')
+        else:
+            output, error = self.__client.runcommand(args)
+        
+        if output:
+            index = 0
+            for line in output.splitlines():
+                index += 1
+                changeset, tags, author, date, branches, parents, bookmarks = \
+                    line.split("@@@")
+                cdate, ctime = date.split()[:2]
+                info.append("""<p><table>""")
+                if mode == "heads":
                     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 bookmarks:
-                        info.append(QApplication.translate("mercurial",
-                            """<tr><td><b>Bookmarks</b></td><td>{0}</td></tr>""")\
-                            .format('<br/>'.join(bookmarks.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())))
+                        """<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 bookmarks:
                     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_()
-    
+                        """<tr><td><b>Bookmarks</b></td><td>{0}</td></tr>""")\
+                        .format('<br/>'.join(bookmarks.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):
         """
@@ -1620,8 +1709,7 @@
             if repodir == os.sep:
                 return False
         
-        dia = HgDialog(
-            self.trUtf8('Resolving files/directories'))
+        dia = HgDialog(self.trUtf8('Resolving files/directories'), self)
         res = dia.startProcess(args, repodir)
         if res:
             dia.exec_()
@@ -1653,7 +1741,8 @@
             args.append('branch')
             args.append(name.strip().replace(" ", "_"))
             
-            dia = HgDialog(self.trUtf8('Creating branch in the Mercurial repository'))
+            dia = HgDialog(self.trUtf8('Creating branch in the Mercurial repository'),
+                           self)
             res = dia.startProcess(args, repodir)
             if res:
                 dia.exec_()
@@ -1676,7 +1765,7 @@
         args = []
         args.append("branch")
         
-        dia = HgDialog(self.trUtf8('Showing current branch'))
+        dia = HgDialog(self.trUtf8('Showing current branch'), self)
         res = dia.startProcess(args, repodir, False)
         if res:
             dia.exec_()
@@ -1733,7 +1822,8 @@
         args = []
         args.append('verify')
         
-        dia = HgDialog(self.trUtf8('Verifying the integrity of the Mercurial repository'))
+        dia = HgDialog(self.trUtf8('Verifying the integrity of the Mercurial repository'),
+                       self)
         res = dia.startProcess(args, repodir)
         if res:
             dia.exec_()
@@ -1757,7 +1847,7 @@
         args.append('showconfig')
         args.append("--untrusted")
         
-        dia = HgDialog(self.trUtf8('Showing the combined configuration settings'))
+        dia = HgDialog(self.trUtf8('Showing the combined configuration settings'), self)
         res = dia.startProcess(args, repodir, False)
         if res:
             dia.exec_()
@@ -1780,7 +1870,7 @@
         args = []
         args.append('paths')
         
-        dia = HgDialog(self.trUtf8('Showing aliases for remote repositories'))
+        dia = HgDialog(self.trUtf8('Showing aliases for remote repositories'), self)
         res = dia.startProcess(args, repodir, False)
         if res:
             dia.exec_()
@@ -1803,7 +1893,7 @@
         args = []
         args.append('recover')
         
-        dia = HgDialog(self.trUtf8('Recovering from interrupted transaction'))
+        dia = HgDialog(self.trUtf8('Recovering from interrupted transaction'), self)
         res = dia.startProcess(args, repodir, False)
         if res:
             dia.exec_()
@@ -1826,7 +1916,7 @@
         args = []
         args.append('identify')
         
-        dia = HgDialog(self.trUtf8('Identifying project directory'))
+        dia = HgDialog(self.trUtf8('Identifying project directory'), self)
         res = dia.startProcess(args, repodir, False)
         if res:
             dia.exec_()
@@ -1947,7 +2037,7 @@
                 args.append(compression)
             args.append(fname)
             
-            dia = HgDialog(self.trUtf8('Create changegroup'))
+            dia = HgDialog(self.trUtf8('Create changegroup'), self)
             res = dia.startProcess(args, repodir)
             if res:
                 dia.exec_()
@@ -2009,7 +2099,7 @@
             args.append('identify')
             args.append(file)
             
-            dia = HgDialog(self.trUtf8('Identifying changegroup file'))
+            dia = HgDialog(self.trUtf8('Identifying changegroup file'), self)
             res = dia.startProcess(args, repodir, False)
             if res:
                 dia.exec_()
@@ -2046,7 +2136,7 @@
                 args.append("--update")
             args.extend(files)
             
-            dia = HgDialog(self.trUtf8('Apply changegroups'))
+            dia = HgDialog(self.trUtf8('Apply changegroups'), self)
             res = dia.startProcess(args, repodir)
             if res:
                 dia.exec_()
@@ -2094,7 +2184,7 @@
         if rev:
             args.append(rev)
         
-        dia = HgDialog(self.trUtf8('Mercurial Bisect ({0})').format(subcommand))
+        dia = HgDialog(self.trUtf8('Mercurial Bisect ({0})').format(subcommand), self)
         res = dia.startProcess(args, repodir)
         if res:
             dia.exec_()
@@ -2127,7 +2217,7 @@
                 return
         
         dia = HgDialog(
-            self.trUtf8('Removing files from the Mercurial repository only'))
+            self.trUtf8('Removing files from the Mercurial repository only'), self)
         res = dia.startProcess(args, repodir)
         if res:
             dia.exec_()
@@ -2182,7 +2272,7 @@
             args.append(message)
             args.append(rev)
             
-            dia = HgDialog(self.trUtf8('Backing out changeset'))
+            dia = HgDialog(self.trUtf8('Backing out changeset'), self)
             res = dia.startProcess(args, repodir)
             if res:
                 dia.exec_()
@@ -2207,7 +2297,7 @@
             self.trUtf8("""Are you sure you want to rollback the last transaction?"""),
             icon=E5MessageBox.Warning)
         if res:
-            dia = HgDialog(self.trUtf8('Rollback last transaction'))
+            dia = HgDialog(self.trUtf8('Rollback last transaction'), self)
             res = dia.startProcess(["rollback"], repodir)
             if res:
                 dia.exec_()
@@ -2241,6 +2331,15 @@
         @param path name of the changed file (string)
         """
         self.__getExtensionsInfo()
+        
+        if self.__client:
+            ok, err = self.__client.restartServer()
+            if not ok:
+                E5MessageBox.warning(None,
+                    self.trUtf8("Mercurial Command Server"),
+                    self.trUtf8("""<p>The Mercurial Command Server could not be"""
+                                """ restarted.</p><p>Reason: {0}</p>""").format(err))
+                self.__client = None
     
     def __monitorRepoIniFile(self, name):
         """
@@ -2269,20 +2368,26 @@
         activeExtensions = sorted(self.__activeExtensions)
         self.__activeExtensions = []
         
-        process = QProcess()
         args = []
         args.append('showconfig')
         args.append('extensions')
-        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')
-                for line in output.splitlines():
-                    extensionName = line.split("=", 1)[0].strip().split(".")[-1].strip()
-                    self.__activeExtensions.append(extensionName)
+        
+        if self.__client is None:
+            process = QProcess()
+            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')
+        else:
+            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.versionStr >= "1.8":
             if "bookmarks" not in self.__activeExtensions:
@@ -2334,7 +2439,19 @@
         """
         self.__projectHelper = self.__plugin.getProjectHelper()
         self.__projectHelper.setObjects(self, project)
-        self.__monitorRepoIniFile(project.ppath)
+        self.__monitorRepoIniFile(project.getProjectPath())
+        
+        if self.versionStr >= "1.9":
+            client = HgClient(project.getProjectPath(), "utf-8", self)
+            ok, err = client.startServer()
+            if ok:
+               self.__client = client
+            else:
+                E5MessageBox.warning(None,
+                    self.trUtf8("Mercurial Command Server"),
+                    self.trUtf8("""<p>The Mercurial Command Server could not be"""
+                                """ started.</p><p>Reason: {0}</p>""").format(err))
+        
         return self.__projectHelper
 
     ############################################################################
--- a/eric5.e4p	Sat Aug 27 11:50:54 2011 +0200
+++ b/eric5.e4p	Sun Aug 28 18:53:13 2011 +0200
@@ -927,6 +927,7 @@
     <Source>Utilities/crypto/py3PBKDF2.py</Source>
     <Source>Preferences/ConfigurationPages/SecurityPage.py</Source>
     <Source>UtilitiesPython2/PySideImporter.py</Source>
+    <Source>Plugins/VcsPlugins/vcsMercurial/HgClient.py</Source>
   </Sources>
   <Forms>
     <Form>PyUnit/UnittestDialog.ui</Form>

eric ide

mercurial