Angel Ezquerra  committed 4d2fde1

workbench, settings: implement workbench reuse (i.e. single workbench window)

This patch adds a setting to enable/disable the creation of a "single workbench
instance". Rather than opening multiple workbench windows (which is slow if you
have a lot of repositories open), the first workbench that is created creates a
QLocalServer which listens to incoming connections.

When this setting is enabled, when the thg workbench is started it will first
try to connect to an existing "thg workbench server". If it cannot connect it
will assume that no other workbench is open, open the workbench window and start
a QLocalServer that will wait for connections from other thg clients. If it
successfully connects to an existing server, it will send the path of the
repository that must be open and exit. The server will receive this path and
open the corresponding repository in the existing workbench window.

A new setting "Single Workbench Window" has been added to the "Workbench"
panel. The default has been set to "False" for now. However, since this greatly
speeds up opening repositories in the workbench, I'd like to change it in the
future, once we add a way to open a new workbench even if this mode is enabled.

For now when this mode is enabled it is still possible to force TortoiseHg to
open a new window by using the new --newworkbench thg command line option.


In order to test it the best way is to enable the "Single Workbench mode" in the
settings, close TortoiseHg, and then open two console windows and run "thg
--nofork" on each of them.

For example, on the first console you could do:

> c:\Python26\python.exe thg --nofork

Then, on the second console you can execute thg and request to open a new repo
on the existing workbench window:

> c:\Python26\python.exe thg --nofork --repository c:\my_repo

At that point you should see the repository 'c:\my_repo' open on the
existing workbench window.

  • Participants
  • Parent commits 5e83f2b

Comments (0)

Files changed (3)

File tortoisehg/hgqt/

     if options['help']:
         return help_(ui, cmd)
+    if options['newworkbench']:
+        cmdoptions['newworkbench'] = True
     path = options['repository']
     if path:
         if path.startswith('bundle:'):
     ('', 'fork', None, _('always fork GUI process')),
     ('', 'listfile', '', _('read file list from file')),
     ('', 'listfileutf8', '', _('read file list from file encoding utf-8')),
+    ('', 'newworkbench', None, _('open a new workbench window')),
 table = {

File tortoisehg/hgqt/

 ({'name': 'log', 'label': _('Workbench'), 'icon': 'menulog'}, (
+    _fi(_('Single Workbench Window'), 'tortoisehg.workbench.single', genBoolRBGroup,
+        _('Select whether you want to have a single workbench window. '
+        'If you disable this setting you will get a new workbench window everytime that you use the "Hg Workbench"'
+        'command on the explorer context menu. Default: True'),
+        restartneeded=True, globalonly=True),
     _fi(_('Default widget'), 'tortoisehg.defaultwidget', (genDefaultCombo,
         ['revdetails', 'commit', 'mq', 'sync', 'manifest', 'search']),
         _('Select the initial widget that will be shown when opening a '

File tortoisehg/hgqt/

 import os
 import sys
+import getpass # used to get the username on the workbench server
 from mercurial import ui
 from mercurial.error import RepoError
 from tortoisehg.util import paths, hglib
 from PyQt4.QtCore import *
 from PyQt4.QtGui import *
+from PyQt4.QtNetwork import QLocalServer, QLocalSocket
 class ThgTabBar(QTabBar):
     def mouseReleaseEvent(self, event):
     finished = pyqtSignal(int)
     activeRepoChanged = pyqtSignal(QString)
-    def __init__(self):
+    def __init__(self, createserver=False):
         self.progressDialog = QProgressDialog('TortoiseHg - Initializing Workbench', QString(), 0, 100)
         self.progressDialog = None
+        self.server = None
+        if createserver:
+            # Enable the Workbench Server that is used to maintain a single workbench instance
+            self.createWorkbenchServer()
     def setupUi(self):
         desktopgeom = qApp.desktop().availableGeometry()
         self.resize(desktopgeom.size() * 0.8)
+            if self.server:
+                self.server.close()
             # mimic QDialog exit
                             parent=self, root=twrepo)
+    def createWorkbenchServer(self):
+        self.server = QLocalServer()
+        self.server.newConnection.connect(self.newConnection)
+        self.server.listen(qApp.applicationName()+ '-' + getpass.getuser())
+    def newConnection(self):
+        socket = self.server.nextPendingConnection()
+        if socket:
+            socket.waitForReadyRead(10000)
+            root = socket.readAll()
+            if root and root != '[echo]':
+                self.openRepo(root, reuse=True)
+            socket.write(QByteArray(root))
+            socket.flush()
+def connectToExistingWorkbench(root=None):
+    """
+    Connect and send data to an existing workbench server
+    For the connection to be successful, the server must loopback the data
+    that we send to it.
+    Normally the data that is sent will be a repository root path, but we can
+    also send "echo" to check that the connection works (i.e. that there is a
+    server)
+    """
+    if root:
+        data = root
+    else:
+        data = '[echo]'
+    socket = QLocalSocket()
+    socket.connectToServer(qApp.applicationName() + '-' + getpass.getuser(),
+        QIODevice.ReadWrite)
+    if socket.waitForConnected(10000):
+        socket.write(QByteArray(data))
+        socket.flush()
+        socket.waitForReadyRead(10000)
+        reply = socket.readAll()
+        if data == reply:
+            return True
+    return False
 def run(ui, *pats, **opts):
     root = opts.get('root') or paths.find_root()
             dlg.setWindowTitle(_('Hg file log viewer [%s] - %s') % (
                 repo.displayname, ufname))
             return dlg
-    w = Workbench()
+    # Before starting the workbench, we must check if we must try to reuse an
+    # existing workbench window (we don't by default)
+    # Note that if the "single workbench mode" is enable, and there is no
+    # existing workbench window, we must tell the Workbench object to create
+    # the workbench server
+    singleworkbenchmode = ui.configbool('tortoisehg', 'workbench.single', False)
+    mustcreateserver = False
+    if singleworkbenchmode:
+        newworkbench = opts.get('newworkbench')
+        if root and not newworkbench:
+            if connectToExistingWorkbench(root):
+                # The were able to connect to an existing workbench server, and
+                # it confirmed that it has opened the selected repo for us
+                exit(0)
+            # there is no pre-existing workbench server
+            serverexists = False
+        else:
+            serverexists = connectToExistingWorkbench('[echo]')
+        # When in " single workbench mode", we must create a server if there
+        # is not one already
+        mustcreateserver = not serverexists
+    w = Workbench(createserver=mustcreateserver)
     if root:
         root = hglib.tounicode(root)
         bundle = opts.get('bundle')