1. TortoiseHg
  2. TortoiseHg
  3. thg

Commits

Steve Borho  committed 22ea6af Merge

Merge with default (Code Freeze)

  • Participants
  • Parent commits d6404ec, 07bd2f5
  • Branches stable

Comments (0)

Files changed (84)

File contrib/thgdebugtools/core.py

View file
  • Ignore whitespace
 from PyQt4.QtCore import *
 from PyQt4.QtGui import *
 
-from mercurial import extensions
-from tortoisehg.hgqt import run, workbench
+from tortoisehg.hgqt import run
 
 import dbgutil, infobar, widgets
 
                                      % ', '.join(map(str, gc.get_count())))
         self._gcEnabledAction.setChecked(self.isGcEnabled())
 
-def _workbenchrun(orig, ui, *pats, **opts):
-    dlg = orig(ui, *pats, **opts)
-    m = dlg.menuBar().addMenu('&Debug')
-    DebugMenuActions(m, parent=dlg)
-    return dlg
+def extsetup(ui):
+    class dbgqtrun(run.qtrun.__class__):
+        def _createdialog(self, dlgfunc, args, opts):
+            dlg, reporoot = super(dbgqtrun, self)._createdialog(dlgfunc, args,
+                                                                opts)
+            if isinstance(dlg, QMainWindow):
+                m = dlg.menuBar().addMenu('&Debug')
+                DebugMenuActions(m, parent=dlg)
+            return dlg, reporoot
 
-def extsetup(ui):
-    extensions.wrapfunction(workbench, 'run', _workbenchrun)
+    run.qtrun.__class__ = dbgqtrun

File contrib/thgdebugtools/dbgutil.py

View file
  • Ignore whitespace
 
     def _getText(self, title, label, text=None):
         newtext, ok = QInputDialog.getText(self._parentWidget(), title, label,
-                                           text=text or '')
+                                           QLineEdit.Normal, text or '')
         if ok:
             return unicode(newtext)
 

File icons/16x16/apps/gnupg.png

  • Ignore whitespace
Added
New image

File icons/32x32/actions/hg-sign.png

  • Ignore whitespace
Added
New image

File tests/graph_test.py

View file
  • Ignore whitespace
+import os
+from nose.tools import *
+
+from mercurial import hg, ui
+from tortoisehg.hgqt import graph
+
+import helpers
+
+def setup():
+    global _tmpdir
+    _tmpdir = helpers.mktmpdir(__name__)
+
+    # foo0 -- foo1 ---------- foo3 -------------------------- foo7
+    #   \       \
+    #    \       -------------------- baz4 -- baz5 -- baz6 --------
+    #     \                                        /               \
+    #      ---------- bar2 ------------------------------------------ bar8
+    #       [branch: bar]
+    hg = helpers.HgClient(os.path.join(_tmpdir, 'named-branch'))
+    hg.init()
+    hg.fappend('data', 'foo0')
+    hg.commit('-Am', 'foo0')
+    hg.fappend('data', 'foo1\n')
+    hg.commit('-m', 'foo1')
+    hg.update('0')
+    hg.branch('bar')
+    hg.fappend('data', 'bar2\n')
+    hg.commit('-m', 'bar2')
+    hg.update('1')
+    hg.fappend('data', 'foo3\n')
+    hg.commit('-m', 'foo3')
+    hg.update('1')
+    hg.fappend('data', 'baz4\n')
+    hg.commit('-m', 'baz4')
+    hg.fappend('data', 'baz5\n')
+    hg.commit('-m', 'baz5')
+    hg.merge('--tool=internal:local', '2')
+    hg.commit('-m', 'baz6')
+    hg.update('3')
+    hg.fappend('data', 'foo7\n')
+    hg.commit('-m', 'foo7')
+    hg.update('2')
+    hg.merge('--tool=internal:local', '6')
+    hg.commit('-m', 'bar8')
+
+def openrepo(name):
+    return hg.repository(ui.ui(), os.path.join(_tmpdir, name))
+
+def buildlinecolortable(grapher):
+    table = {}  # rev: [linecolor, ...]
+    for node in grapher:
+        if not node:
+            continue
+        colors = [color for start, end, color, _linetype, _children, _rev
+                  in sorted(node.bottomlines)]  # in (start, end) order
+        table[node.rev] = colors
+    return table
+
+def test_linecolor_unfiltered():
+    repo = openrepo('named-branch')
+    grapher = graph.revision_grapher(repo)
+    actualtable = buildlinecolortable(grapher)
+    expectedtable = {
+        None: [0],        # |
+        8: [0, 2],        # |\
+        7: [0, 2, 3],     # | | |
+        6: [0, 0, 4, 3],  # |/| |
+        5: [0, 4, 3],     # | | |
+        4: [0, 3, 3],     # | | |
+        3: [0, 3, 3],     # | |/
+        2: [0, 3],        # | |
+        1: [0, 0],        # |/
+        0: [],
+        9: [0],  # TODO bug?
+        }
+    assert_equal(expectedtable, actualtable)
+
+def test_linecolor_branchfiltered():
+    repo = openrepo('named-branch')
+    grapher = graph.revision_grapher(repo, branch='default')
+    actualtable = buildlinecolortable(grapher)
+    expectedtable = {
+        7: [1],     # |
+        6: [1, 2],  # | |
+        5: [1, 2],  # | |
+        4: [1, 1],  # | |
+        3: [1, 1],  # |/
+        1: [1],     # |
+        0: [],
+        9: [],  # TODO bug?
+        }
+    assert_equal(expectedtable, actualtable)
+
+def test_linecolor_filelog():
+    repo = openrepo('named-branch')
+    grapher = graph.filelog_grapher(repo, 'data')
+    actualtable = buildlinecolortable(grapher)
+    expectedtable = {
+        7: [0],        # |
+        6: [0, 3, 2],  # | |\
+        5: [0, 3, 2],  # | | |
+        4: [0, 3, 2],  # | | |
+        3: [2, 3, 2],  #  X /
+        2: [3, 2],     # | |
+        1: [3, 3],     # |/
+        0: [],
+        }
+    assert_equal(expectedtable, actualtable)

File tests/nosehgenv.py

View file
  • Ignore whitespace
             # https://bitbucket.org/tortoisehg/thg/issue/1783/
             from tortoisehg.hgqt import thgrepo
             for e in thgrepo._repocache.itervalues():
-                w = e._pyqtobj.watcher
-                w.removePaths(w.directories())
-                w.removePaths(w.files())
+                repoagent = e._pyqtobj
+                repoagent.stopMonitoring()
 
             shutil.rmtree(self.tmpdir)

File tests/qt_repomanager_test.py

View file
  • Ignore whitespace
+import mock, unittest
+from mercurial import ui
+from tortoisehg.hgqt import thgrepo
+
+def mockrepo(ui, path):
+    m = mock.MagicMock(ui=ui, root=path)
+    m.unfiltered = lambda: m
+    return m
+
+LOCAL_SIGNALS = ['repositoryOpened', 'repositoryClosed']
+MAPPED_SIGNALS = ['configChanged', 'repositoryChanged', 'repositoryDestroyed']
+
+class RepoManagerMockedTest(unittest.TestCase):
+    def setUp(self):
+        self.hgrepopatcher = mock.patch('mercurial.hg.repository', new=mockrepo)
+        self.watcherpatcher = mock.patch('tortoisehg.hgqt.thgrepo.RepoWatcher')
+        self.hgrepopatcher.start()
+        self.watcherpatcher.start()
+        self.repoman = thgrepo.RepoManager(ui.ui())
+
+        for signame in LOCAL_SIGNALS + MAPPED_SIGNALS:
+            slot = mock.Mock()
+            setattr(self, signame, slot)
+            getattr(self.repoman, signame).connect(slot)
+
+    def tearDown(self):
+        self.watcherpatcher.stop()
+        self.hgrepopatcher.stop()
+        thgrepo._repocache.clear()
+
+    def test_cached(self):
+        a1 = self.repoman.openRepoAgent('/a')
+        a2 = self.repoman.openRepoAgent('/a')
+        self.assertTrue(a1 is a2)
+
+    def test_release(self):
+        self.repoman.openRepoAgent('/a')
+        self.repoman.openRepoAgent('/a')
+
+        self.repoman.releaseRepoAgent('/a')
+        self.assertTrue(self.repoman.repoAgent('/a'))
+
+        self.repoman.releaseRepoAgent('/a')
+        self.assertFalse(self.repoman.repoAgent('/a'))
+
+    def test_signal_map(self):
+        a = self.repoman.openRepoAgent('/a')
+        for signame in MAPPED_SIGNALS:
+            getattr(a, signame).emit()
+            getattr(self, signame).assert_called_once_with('/a')
+
+    def test_disconnect_signal_on_close(self):
+        a = self.repoman.openRepoAgent('/a')
+        self.repoman.releaseRepoAgent('/a')
+        for signame in MAPPED_SIGNALS:
+            getattr(a, signame).emit()
+            self.assertFalse(getattr(self, signame).called)
+
+    def test_opened_signal(self):
+        self.repoman.repositoryOpened.connect(
+            lambda: self.assertTrue(self.repoman.repoAgent('/a')))
+        self.repoman.openRepoAgent('/a')
+        self.repositoryOpened.assert_called_once_with('/a')
+        self.repositoryOpened.reset_mock()
+        # emitted only if repository is actually instantiated (i.e. not cached)
+        self.repoman.openRepoAgent('/a')
+        self.assertFalse(self.repositoryOpened.called)
+
+    def test_closed_signal(self):
+        self.repoman.repositoryClosed.connect(
+            lambda: self.assertFalse(self.repoman.repoAgent('/a')))
+        self.repoman.openRepoAgent('/a')
+        self.repoman.openRepoAgent('/a')
+        self.repoman.releaseRepoAgent('/a')
+        self.assertFalse(self.repositoryClosed.called)
+        self.repoman.releaseRepoAgent('/a')
+        self.repositoryClosed.assert_called_once_with('/a')

File tortoisehg/hgqt/about.py

View file
  • Ignore whitespace
     def _writesettings(self):
         s = QSettings()
         s.setValue('about/geom', self.saveGeometry())
-
-def run(ui, *pats, **opts):
-    return AboutDialog()

File tortoisehg/hgqt/archive.py

View file
  • Ignore whitespace
 from PyQt4.QtCore import *
 from PyQt4.QtGui import *
 
-from mercurial import hg, error
+from mercurial import error
 
 from tortoisehg.hgqt.i18n import _
-from tortoisehg.util import hglib, paths
-from tortoisehg.hgqt import cmdui, qtlib, thgrepo
+from tortoisehg.util import hglib
+from tortoisehg.hgqt import cmdui, qtlib
 
 WD_PARENT = _('= Working Directory Parent =')
 
     makeLogVisible = pyqtSignal(bool)
     progress = pyqtSignal(QString, object, QString, QString, object)
 
-    def __init__(self, ui, repo, rev=None, parent=None):
+    def __init__(self, repo, rev=None, parent=None):
         super(ArchiveDialog, self).__init__(parent)
 
         # main layout
         self.vbox.addLayout(self.hbox)
 
         # set default values
-        self.ui = ui
         self.repo = repo
-        self.initrev = rev
         self.prevtarget = None
         self.rev_combo.addItem(WD_PARENT)
         for b in self.repo.branchtags():
         tags.sort(reverse=True)
         for t in tags:
             self.rev_combo.addItem(hglib.tounicode(t))
-        if self.initrev:
-            text = hglib.tounicode(str(self.initrev))
-            selectindex = self.rev_combo.findText(text, Qt.MatchFlags(Qt.MatchExactly))
+        if rev:
+            text = hglib.tounicode(str(rev))
+            selectindex = self.rev_combo.findText(text)
             if selectindex >= 0:
                 self.rev_combo.setCurrentIndex(selectindex)
             else:
-                self.rev_combo.insertItems(0, [text])
+                self.rev_combo.insertItem(0, text)
                 self.rev_combo.setCurrentIndex(0)
         self.rev_combo.setMaxVisibleItems(self.rev_combo.count())
         self.subrepos_chk.setChecked(self.get_subrepos_present())
 
         # connecting slots
         self.dest_edit.textEdited.connect(self.dest_edited)
-        self.connect(self.rev_combo, SIGNAL('currentIndexChanged(int)'),
-                     self.rev_combo_changed)
-        self.connect(self.rev_combo, SIGNAL('editTextChanged(QString)'),
-                     self.rev_combo_changed)
+        self.rev_combo.editTextChanged.connect(self.rev_combo_changed)
         self.dest_btn.clicked.connect(self.browse_clicked)
         self.files_in_rev_chk.stateChanged.connect(self.dest_edited)
         self.subrepos_chk.toggled.connect(self.onSubreposToggled)
         self.rev_combo.setFocus()
         self._readsettings()
 
-    def rev_combo_changed(self, index):
+    def rev_combo_changed(self):
         self.subrepos_chk.setChecked(self.get_subrepos_present())
         self.update_path()
 
             ctx = self.repo[rev]
         except (error.LookupError, error.RepoLookupError):
             return False
-        return '.hgsubstate' in ctx.files() or '.hgsubstate' in ctx.manifest()
+        return '.hgsubstate' in ctx
 
     def get_selected_rev(self):
         rev = self.rev_combo.currentText()
     def _writesettings(self):
         s = QSettings()
         s.setValue('archive/geom', self.saveGeometry())
-
-def run(ui, *revs, **opts):
-    rev = opts.get('rev')
-    repo = thgrepo.repository(ui, paths.find_root())
-    return ArchiveDialog(repo.ui, repo, rev)

File tortoisehg/hgqt/backout.py

View file
  • Ignore whitespace
 
     def cancel(self):
         self.canceled = True
-
-
-def run(ui, *pats, **opts):
-    from tortoisehg.util import paths
-    repo = thgrepo.repository(ui, path=paths.find_root())
-    if opts.get('rev'):
-        rev = opts.get('rev')
-    elif len(pats) == 1:
-        rev = pats[0]
-    else:
-        rev = 'tip'
-    return BackoutDialog(rev, repo, None)

File tortoisehg/hgqt/bisect.py

View file
  • Ignore whitespace
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2, incorporated herein by reference.
 
-import os
-
 from PyQt4.QtCore import *
 from PyQt4.QtGui import *
 
 
         hbox = QHBoxLayout()
         hbox.addWidget(QLabel(_('Known good revision:')))
-        gle = QLineEdit()
+        self._gle = gle = QLineEdit()
         gle.setText(opts.get('good', ''))
         hbox.addWidget(gle, 1)
-        gb = QPushButton(_('Accept'))
+        self._gb = gb = QPushButton(_('Accept'))
         hbox.addWidget(gb)
         box.addLayout(hbox)
 
         hbox = QHBoxLayout()
         hbox.addWidget(QLabel(_('Known bad revision:')))
-        ble = QLineEdit()
+        self._ble = ble = QLineEdit()
         ble.setText(opts.get('bad', ''))
         ble.setEnabled(False)
         hbox.addWidget(ble, 1)
-        bb = QPushButton(_('Accept'))
+        self._bb = bb = QPushButton(_('Accept'))
         bb.setEnabled(False)
         hbox.addWidget(bb)
         box.addLayout(hbox)
 
         hbox = QHBoxLayout()
         box.addLayout(hbox)
-        lbl = QLabel()
+        self._lbl = lbl = QLabel()
         hbox.addWidget(lbl)
         hbox.addStretch(1)
         closeb = QPushButton(_('Close'))
             b.setEnabled(False)
         self.lastrev = None
 
-        def cmdFinished(ret):
-            if ret != 0:
-                lbl.setText(_('Error encountered.'))
-                return
-            repo.dirstate.invalidate()
-            ctx = repo['.']
-            if ctx.rev() == self.lastrev:
-                lbl.setText(_('Culprit found.'))
-                return
-            self.lastrev = ctx.rev()
-            for b in self.nextbuttons:
-                b.setEnabled(True)
-            lbl.setText('%s: %d (%s) -> %s' % (_('Revision'), ctx.rev(), ctx,
-                        _('Test this revision and report findings. '
-                          '(good/bad/skip)')))
-        self.cmd.commandFinished.connect(cmdFinished)
+        self.cmd.commandFinished.connect(self._cmdFinished)
 
-        prefix = ['bisect', '--repository', repo.root]
+        gb.pressed.connect(self._verifyGood)
+        bb.pressed.connect(self._verifyBad)
+        gle.returnPressed.connect(self._verifyGood)
+        ble.returnPressed.connect(self._verifyBad)
 
-        def gverify():
-            good = hglib.fromunicode(gle.text().simplified())
-            try:
-                ctx = repo[good]
-                self.goodrev = ctx.rev()
-                gb.setEnabled(False)
-                gle.setEnabled(False)
-                bb.setEnabled(True)
-                ble.setEnabled(True)
-                ble.setFocus()
-            except error.RepoLookupError, e:
-                self.cmd.core.stbar.showMessage(hglib.tounicode(str(e)))
-            except util.Abort, e:
-                if e.hint:
-                    err = _('%s (hint: %s)') % (hglib.tounicode(str(e)),
-                                                hglib.tounicode(e.hint))
-                else:
-                    err = hglib.tounicode(str(e))
-                self.cmd.core.stbar.showMessage(err)
-        def bverify():
-            bad = hglib.fromunicode(ble.text().simplified())
-            try:
-                ctx = repo[bad]
-                self.badrev = ctx.rev()
-                ble.setEnabled(False)
-                bb.setEnabled(False)
-                cmds = []
-                cmds.append(prefix + ['--reset'])
-                cmds.append(prefix + ['--good', str(self.goodrev)])
-                cmds.append(prefix + ['--bad', str(self.badrev)])
-                self.cmd.run(*cmds)
-            except error.RepoLookupError, e:
-                self.cmd.core.stbar.showMessage(hglib.tounicode(str(e)))
-            except util.Abort, e:
-                if e.hint:
-                    err = _('%s (hint: %s)') % (hglib.tounicode(str(e)),
-                                                hglib.tounicode(e.hint))
-                else:
-                    err = hglib.tounicode(str(e))
-                self.cmd.core.stbar.showMessage(err)
-
-        gb.pressed.connect(gverify)
-        bb.pressed.connect(bverify)
-        gle.returnPressed.connect(gverify)
-        ble.returnPressed.connect(bverify)
-
-        def goodrevision():
-            for b in self.nextbuttons:
-                b.setEnabled(False)
-            self.cmd.run(prefix + ['--good', '.'])
-        def badrevision():
-            for b in self.nextbuttons:
-                b.setEnabled(False)
-            self.cmd.run(prefix + ['--bad', '.'])
-        def skiprevision():
-            for b in self.nextbuttons:
-                b.setEnabled(False)
-            self.cmd.run(prefix + ['--skip', '.'])
-        goodrev.clicked.connect(goodrevision)
-        badrev.clicked.connect(badrevision)
-        skiprev.clicked.connect(skiprevision)
+        goodrev.clicked.connect(self._markGoodRevision)
+        badrev.clicked.connect(self._markBadRevision)
+        skiprev.clicked.connect(self._skipRevision)
 
     def keyPressEvent(self, event):
         if event.key() == Qt.Key_Escape:
             self.reject()
         super(BisectDialog, self).keyPressEvent(event)
 
+    def _bisectcmd(self, *args, **opts):
+        opts['repository'] = self.repo.root
+        return hglib.buildcmdargs('bisect', *args, **opts)
 
-def run(ui, *pats, **opts):
-    from tortoisehg.util import paths
-    from tortoisehg.hgqt import thgrepo
-    repo = thgrepo.repository(ui, path=paths.find_root())
-    return BisectDialog(repo, opts)
+    @pyqtSlot(int)
+    def _cmdFinished(self, ret):
+        lbl = self._lbl
+        if ret != 0:
+            lbl.setText(_('Error encountered.'))
+            return
+        self.repo.dirstate.invalidate()
+        ctx = self.repo['.']
+        if ctx.rev() == self.lastrev:
+            lbl.setText(_('Culprit found.'))
+            return
+        self.lastrev = ctx.rev()
+        for b in self.nextbuttons:
+            b.setEnabled(True)
+        lbl.setText('%s: %d (%s) -> %s' % (_('Revision'), ctx.rev(), ctx,
+                    _('Test this revision and report findings. '
+                      '(good/bad/skip)')))
+
+    @pyqtSlot()
+    def _verifyGood(self):
+        good = hglib.fromunicode(self._gle.text().simplified())
+        try:
+            ctx = self.repo[good]
+            self.goodrev = ctx.rev()
+            self._gb.setEnabled(False)
+            self._gle.setEnabled(False)
+            self._bb.setEnabled(True)
+            self._ble.setEnabled(True)
+            self._ble.setFocus()
+        except (error.LookupError, error.RepoLookupError), e:
+            self.cmd.core.stbar.showMessage(hglib.tounicode(str(e)))
+        except util.Abort, e:
+            if e.hint:
+                err = _('%s (hint: %s)') % (hglib.tounicode(str(e)),
+                                            hglib.tounicode(e.hint))
+            else:
+                err = hglib.tounicode(str(e))
+            self.cmd.core.stbar.showMessage(err)
+
+    @pyqtSlot()
+    def _verifyBad(self):
+        bad = hglib.fromunicode(self._ble.text().simplified())
+        try:
+            ctx = self.repo[bad]
+            self.badrev = ctx.rev()
+            self._ble.setEnabled(False)
+            self._bb.setEnabled(False)
+            cmds = []
+            cmds.append(self._bisectcmd(reset=True))
+            cmds.append(self._bisectcmd(self.goodrev, good=True))
+            cmds.append(self._bisectcmd(self.badrev, bad=True))
+            self.cmd.run(*cmds)
+        except (error.LookupError, error.RepoLookupError), e:
+            self.cmd.core.stbar.showMessage(hglib.tounicode(str(e)))
+        except util.Abort, e:
+            if e.hint:
+                err = _('%s (hint: %s)') % (hglib.tounicode(str(e)),
+                                            hglib.tounicode(e.hint))
+            else:
+                err = hglib.tounicode(str(e))
+            self.cmd.core.stbar.showMessage(err)
+
+    @pyqtSlot()
+    def _markGoodRevision(self):
+        for b in self.nextbuttons:
+            b.setEnabled(False)
+        self.cmd.run(self._bisectcmd('.', good=True))
+
+    @pyqtSlot()
+    def _markBadRevision(self):
+        for b in self.nextbuttons:
+            b.setEnabled(False)
+        self.cmd.run(self._bisectcmd('.', bad=True))
+
+    @pyqtSlot()
+    def _skipRevision(self):
+        for b in self.nextbuttons:
+            b.setEnabled(False)
+        self.cmd.run(self._bisectcmd('.', skip=True))

File tortoisehg/hgqt/bookmark.py

View file
  • Ignore whitespace
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2, incorporated herein by reference.
 
-import os
-
 from PyQt4.QtCore import *
 from PyQt4.QtGui import *
 
-from mercurial import error
-
-from tortoisehg.util import hglib, i18n
+from tortoisehg.util import hglib
 from tortoisehg.hgqt.i18n import _
 from tortoisehg.hgqt import qtlib, cmdui
 
-keep = i18n.keepgettext()
-
 class BookmarkDialog(QDialog):
     showMessage = pyqtSignal(QString)
     output = pyqtSignal(QString, QString)
     makeLogVisible = pyqtSignal(bool)
 
-    def __init__(self, repo, rev, parent):
+    def __init__(self, repo, rev, parent=None):
         super(BookmarkDialog, self).__init__(parent)
         self.setWindowFlags(self.windowFlags() & \
                             ~Qt.WindowContextHelpButtonHint)
         self.bookmarkCombo.setFocus()
         self.bookmarkTextChanged()
 
+    @pyqtSlot()
     def refresh(self):
         """ update display on dialog with recent repo data """
         # add bookmarks to drop-down list
             self.renameBtn.setEnabled(False)
             self.newNameEdit.setEnabled(False)
 
+    def setBookmarkName(self, name):
+        self.bookmarkCombo.setEditText(name)
+
     def set_status(self, text, icon=None):
         self.status.setShown(True)
         self.sep.setShown(True)
                    '--rename', namelocal, newnamelocal]
         self.cmd.run(cmdline)
         self.finishfunc = finished
-
-    def reject(self):
-        # prevent signals from reaching deleted objects
-        self.repo.repositoryChanged.disconnect(self.refresh)
-        super(BookmarkDialog, self).reject()

File tortoisehg/hgqt/chunks.py

View file
  • Ignore whitespace
         dlg = visdiff.visualdiff(self.repo.ui, self.repo, filenames, opts)
         if dlg:
             dlg.exec_()
-            dlg.deleteLater()
 
     def revertfile(self):
         filenames = self.getSelectedFiles()

File tortoisehg/hgqt/clone.py

View file
  • Ignore whitespace
 
         self.composeCommand()
 
+    def setSource(self, url):
+        assert not self.isRunning()
+        self.src_combo.setEditText(url)
+
+    def setDestination(self, url):
+        assert not self.isRunning()
+        self.dest_combo.setEditText(url)
+
+    def isRunning(self):
+        return self.cmd.core.running()
+
     ### Private Methods ###
 
     def getSrc(self):
             self.cmd.cancel()
             return
         QDialog.reject(self)
-
-def run(ui, *pats, **opts):
-    return CloneDialog(pats, opts)

File tortoisehg/hgqt/cmdui.py

View file
  • Ignore whitespace
             if self.extproc:
                 return self.extproc.state() != QProcess.NotRunning
             elif self.thread:
-                return self.thread.isRunning()
+                # keep "running" until just before emitting commandFinished.
+                # thread.isRunning() is cleared earlier than onThreadFinished,
+                # because inter-thread signal is queued.
+                return True
         except AttributeError:
             pass
         return False

File tortoisehg/hgqt/commit.py

View file
  • Ignore whitespace
 import re
 import tempfile
 
-from mercurial import ui, util, error, scmutil, phases
+from mercurial import util, error, scmutil, phases
 
-from tortoisehg.util import hglib, shlib, wconfig, hgversion
+from tortoisehg.util import hglib, shlib, wconfig
 
 from tortoisehg.hgqt.i18n import _
 from tortoisehg.hgqt.messageentry import MessageEntry
+from tortoisehg.hgqt import thgrepo
 from tortoisehg.hgqt import qtlib, qscilib, status, cmdui, branchop, revpanel
 from tortoisehg.hgqt import hgrcutil, mqutil, lfprompt, i18n, partialcommit
 
     progress = pyqtSignal(QString, object, QString, QString, object)
     output = pyqtSignal(QString, QString)
     makeLogVisible = pyqtSignal(bool)
-    beginSuppressPrompt = pyqtSignal()
-    endSuppressPrompt = pyqtSignal()
 
     def __init__(self, repo, pats, opts, embedded=False, parent=None, rev=None):
         QWidget.__init__(self, parent)
         self.runner.output.connect(self.output)
         self.runner.progress.connect(self.progress)
         self.runner.makeLogVisible.connect(self.makeLogVisible)
-        self.runner.commandStarted.connect(self.beginSuppressPrompt)
-        self.runner.commandFinished.connect(self.endSuppressPrompt)
         self.runner.commandFinished.connect(self.commandFinished)
 
         layout = QVBoxLayout()
             if ispatch(r):
                 return False
             ctx = r.changectx('.')
-            canamendctx = (ctx.phase() != phases.public) \
+            return (ctx.phase() != phases.public) \
                 and len(r.changectx(None).parents()) < 2 \
                 and not ctx.children()
-            # hg < 2.6
-            if hgversion.hgversion < '2.6':
-                canamendctx = canamendctx \
-                    and len(ctx.parents()) < 2
-            return canamendctx
 
         acts = [
             ('commit', _('Commit changes'), _('Commit'), notpatch),
         self.committb.setText(curraction._text)
         self.lastAction = curraction._name
 
-    def getBranchCommandLine(self, branchName, repo):
+    def getBranchCommandLine(self):
         '''
         Create the command line to change or create the selected branch unless
         it is the selected branch
         the selected action
         '''
         # This function is used both by commit() and mqPerformAction()
+        repo = self.repo
         commandlines = []
         newbranch = False
         branch = hglib.fromunicode(self.branchop)
         # Check if we need to change branch first
         wholecmdlines = []  # [[cmd1, ...], [cmd2, ...], ...]
         if self.branchop:
-            cmdlines, newbranch = self.getBranchCommandLine(self.branchop,
-                                                            self.repo)
+            cmdlines, newbranch = self.getBranchCommandLine()
             if cmdlines is None:
                 return
             wholecmdlines.extend(cmdlines)
         elif self.branchop == False:
             brcmd = ['--close-branch']
         else:
-            commandlines, newbranch = self.getBranchCommandLine(self.branchop,
-                                                                self.repo)
+            commandlines, newbranch = self.getBranchCommandLine()
             if commandlines is None:
                 return
         partials = []
         toplayout.addWidget(self.bb)
         layout.addWidget(self.statusbar)
 
+        self._subdialogs = qtlib.DialogKeeper(CommitDialog._createSubDialog,
+                                              parent=self)
+
         s = QSettings()
         self.restoreGeometry(s.value('commit/geom').toByteArray())
         commit.loadSettings(s, 'committool')
         qtlib.newshortcutsforstdkey(QKeySequence.Refresh, self, self.refresh)
 
     def linkActivated(self, link):
-        link = hglib.fromunicode(link)
+        link = unicode(link)
         if link.startswith('repo:'):
-            from tortoisehg.hgqt.run import qtrun
-            qtrun(run, ui.ui(), root=link[len('repo:'):])
+            self._subdialogs.open(link[len('repo:'):])
+
+    def _createSubDialog(self, uroot):
+        repo = thgrepo.repository(None, hglib.fromunicode(uroot))
+        return CommitDialog(repo, [], {}, parent=self)
 
     @pyqtSlot()
     def updateUndo(self):
     def reject(self):
         if self.promptExit():
             QDialog.reject(self)
-
-def run(ui, *pats, **opts):
-    from tortoisehg.util import paths
-    from tortoisehg.hgqt import thgrepo
-    root = opts.get('root', paths.find_root())
-    repo = thgrepo.repository(ui, path=root)
-    pats = hglib.canonpaths(pats)
-    os.chdir(repo.root)
-    return CommitDialog(repo, pats, opts)

File tortoisehg/hgqt/compress.py

View file
  • Ignore whitespace
 from PyQt4.QtCore import *
 from PyQt4.QtGui import *
 
-import os
+from mercurial import revset
 
-from mercurial import revset, merge as mergemod
-
-from tortoisehg.util import hglib
 from tortoisehg.hgqt.i18n import _
 from tortoisehg.hgqt import qtlib, csinfo, cmdui, commit, thgrepo
 
                         if status == 'u':
                             self.dirty = True
                             break
-        def completed():
-            self.th.wait()
-            if self.th.dirty:
-                self.compressbtn.setEnabled(False)
-                txt = _('Before compress, you must <a href="commit">'
-                        '<b>commit</b></a> or <a href="discard">'
-                        '<b>discard</b></a> changes.')
-            else:
-                self.compressbtn.setEnabled(True)
-                txt = _('You may continue the compress')
-            self.showMessage.emit(txt)
+
         self.th = CheckThread(self)
-        self.th.finished.connect(completed)
+        self.th.finished.connect(self._checkCompleted)
         self.th.start()
 
+    @pyqtSlot()
+    def _checkCompleted(self):
+        self.th.wait()
+        if self.th.dirty:
+            self.compressbtn.setEnabled(False)
+            txt = _('Before compress, you must <a href="commit">'
+                    '<b>commit</b></a> or <a href="discard">'
+                    '<b>discard</b></a> changes.')
+        else:
+            self.compressbtn.setEnabled(True)
+            txt = _('You may continue the compress')
+        self.showMessage.emit(txt)
+
     def compress(self):
         self.cancelbtn.setShown(False)
         uc = ['update', '--repository', self.repo.root, '--clean', '--rev',

File tortoisehg/hgqt/csinfo.py

View file
  • Ignore whitespace
 from tortoisehg.hgqt import qtlib, thgrepo
 
 PANEL_DEFAULT = ('rev', 'summary', 'user', 'dateage', 'branch', 'close',
-                 'tags', 'graft', 'transplant', 'p4', 'svn', 'converted')
+                 'tags', 'graft', 'transplant', 'obsolete',
+                 'p4', 'svn', 'converted',)
 
 def create(repo, target=None, style=None, custom=None, **kargs):
     return Factory(repo, custom, style, target, **kargs)()
               'tags': _('Tags:'), 'rawbranch': _('Branch:'),
               'rawtags': _('Tags:'), 'graft': _('Graft:'),
               'transplant': _('Transplant:'),
+              'obsolete': _('Obsolete state:'),
               'p4': _('Perforce:'), 'svn': _('Subversion:'),
               'converted': _('Converted From:'), 'shortuser': _('User:')}
 
             elif item == 'desc':
                 return hglib.tounicode(ctx.description().replace('\0', ''))
             elif item == 'summary':
-                value = ctx.description().replace('\0', '').split('\n')[0]
-                if len(value) == 0:
+                summary = hglib.longsummary(
+                    ctx.description().replace('\0', ''))
+                if len(summary) == 0:
                     return None
-                return hglib.tounicode(value)[:80]
+                return summary
             elif item == 'user':
                 user = hglib.user(ctx)
                 if user:
                 except KeyError:
                     pass
                 return None
+            elif item == 'obsolete':
+                obsoletestate = []
+                if ctx.obsolete():
+                    obsoletestate.append('obsolete')
+                if ctx.extinct():
+                    obsoletestate.append('extinct')
+                obsoletestate += ctx.troubles()
+                if obsoletestate:
+                    return obsoletestate
+                return None
             elif item == 'p4':
                 extra = ctx.extra()
                 p4cl = extra.get('p4', None)
                 return qtlib.markup(value)
             elif item == 'dateage':
                 return qtlib.markup('%s (%s)' % value)
+            elif item == 'obsolete':
+                opts = dict(fg='black', bg='#ff8566')
+                obsoletestates = [qtlib.markup(' %s ' % state, **opts)
+                                  for state in value]
+                return ' '.join(obsoletestates)
             raise UnknownItem(item)
         value = self.get_data(item, *args)
         if value is None:
 
         if self.revlabel is None:
             self.revlabel = QLabel()
-            self.revlabel.linkActivated.connect(
-                 lambda s: self.linkActivated.emit(s))
+            self.revlabel.linkActivated.connect(self.linkActivated)
             self.layout().addWidget(self.revlabel, 0, Qt.AlignTop)
 
         if 'expandable' in self.csstyle and self.csstyle['expandable']:

File tortoisehg/hgqt/dnd.py

  • Ignore whitespace
-# dnd.py - TortoiseHg's Drag and Drop handling
-#
-# Copyright 2011 Daniel Atallah <daniel.atallah@gmail.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-from tortoisehg.util import hglib, paths
-from tortoisehg.hgqt import thgrepo, quickop
-
-def __do_run(ui, command, *pats, **_opts):
-    root = paths.find_root()
-    repo = thgrepo.repository(ui, root)
-
-    pats = hglib.canonpaths(pats)
-
-    cmdline = [command] + pats
- 
-    instance = quickop.HeadlessQuickop(repo, cmdline)
-    return instance
-
-def run_copy(ui, *pats, **opts):
-    return __do_run(ui, "copy", *pats, **opts)
-
-def run_move(ui, *pats, **opts):
-    return __do_run(ui, "move", *pats, **opts)

File tortoisehg/hgqt/docklog.py

View file
  • Ignore whitespace
 from mercurial import commands, util
 
 from tortoisehg.hgqt.i18n import _
-from tortoisehg.hgqt import cmdui, run
+from tortoisehg.hgqt import cmdui
 from tortoisehg.util import hglib
 
 class _LogWidgetForConsole(cmdui.LogWidget):
         self.layout().setContentsMargins(0, 0, 0, 0)
         self._initlogwidget()
         self.setFocusProxy(self._logwidget)
-        self.setRepository(None)
+        self._repoagent = None
         self.openPrompt()
-        self.suppressPrompt = False
         self._commandHistory = []
         self._commandIdx = 0
 
             self._logwidget.flash()
 
     def _commandComplete(self, cmdtype, cmdline):
+        from tortoisehg.hgqt import run
         matches = []
         cmd = cmdline.split()
         if cmdtype == 'hg':
         try:
             self._logwidget.appendLog(msg, label)
         finally:
-            if not self.suppressPrompt:
+            if not self._repoagent or not self._repoagent.isBusy():
                 self.openPrompt()
 
-    @pyqtSlot(object)
-    def setRepository(self, repo):
+    def setRepoAgent(self, repoagent):
         """Change the current working repository"""
-        self._repo = repo
+        if self._repoagent:
+            self._repoagent.busyChanged.disconnect(self._suppressPromptOnBusy)
+        self._repoagent = repoagent
+        repoagent.busyChanged.connect(self._suppressPromptOnBusy)
+        repo = repoagent.rawRepo()
         self._logwidget.setPrompt('%s%% ' % (repo and repo.displayname or ''))
 
+    def repoRootPath(self):
+        if self._repoagent:
+            return self._repoagent.rootPath()
+
+    @property
+    def _repo(self):
+        if self._repoagent:
+            return self._repoagent.rawRepo()
+
     @property
     def cwd(self):
         """Return the current working directory"""
         return self._repo and self._repo.root or os.getcwd()
 
+    @pyqtSlot(bool)
+    def _suppressPromptOnBusy(self, busy):
+        if busy:
+            self._logwidget.clearPrompt()
+        else:
+            self.openPrompt()
+
     @pyqtSlot(unicode, object, unicode, unicode, object)
     def _emitProgress(self, *args):
         self.progressReceived.emit(
 
     @_cmdtable
     def _cmd_thg(self, args):
+        from tortoisehg.hgqt import run
         self.closePrompt()
         try:
             if self._repo:
         self.closeRequested.emit()
 
 class LogDockWidget(QDockWidget):
-    visibilityChanged = pyqtSignal(bool)
 
-    def __init__(self, parent=None):
+    progressReceived = pyqtSignal(QString, object, QString, QString,
+                                  object, object)
+
+    def __init__(self, repomanager, parent=None):
         super(LogDockWidget, self).__init__(parent)
 
         self.setFeatures(QDockWidget.DockWidgetClosable |
         self.setWindowTitle(_('Output Log'))
         # Not enabled until we have a way to make it configurable
         #self.setWindowFlags(Qt.Drawer)
+        self.dockLocationChanged.connect(self._updateTitleBarStyle)
 
-        self.logte = ConsoleWidget(self)
-        self.logte.closeRequested.connect(self.close)
-        self.setWidget(self.logte)
-        for name in ('setRepository', 'progressReceived'):
-            setattr(self, name, getattr(self.logte, name))
+        self._repomanager = repomanager
+        self._repomanager.repositoryOpened.connect(self._createConsoleFor)
+        self._repomanager.repositoryClosed.connect(self._destroyConsoleFor)
 
-        self.visibilityChanged.connect(
-            lambda visible: visible and self.logte.setFocus())
+        self._consoles = QStackedWidget(self)
+        self.setWidget(self._consoles)
+        self._createConsole()
+        for root in self._repomanager.repoRootPaths():
+            self._createConsoleFor(root)
+
+        # move focus only when console is activated by keyboard/mouse operation
+        self.toggleViewAction().triggered.connect(self._setFocusOnToggleView)
+
+    def setCurrentRepoRoot(self, root):
+        w = self._findConsoleFor(root)
+        self._consoles.setCurrentWidget(w)
+
+    def _findConsoleFor(self, root):
+        for i in xrange(self._consoles.count()):
+            w = self._consoles.widget(i)
+            if w.repoRootPath() == root:
+                return w
+        raise ValueError('no console found for %r' % root)
+
+    def _createConsole(self):
+        w = ConsoleWidget(self)
+        w.closeRequested.connect(self.close)
+        w.progressReceived.connect(self.progressReceived)
+        self._consoles.addWidget(w)
+        return w
+
+    @pyqtSlot(unicode)
+    def _createConsoleFor(self, root):
+        root = unicode(root)
+        w = self._createConsole()
+        repoagent = self._repomanager.repoAgent(root)
+        assert repoagent
+        w.setRepoAgent(repoagent)
+
+    @pyqtSlot(unicode)
+    def _destroyConsoleFor(self, root):
+        root = unicode(root)
+        w = self._findConsoleFor(root)
+        self._consoles.removeWidget(w)
+        w.setParent(None)
 
     @pyqtSlot()
     def clear(self):
-        self.logte.clear()
+        w = self._consoles.currentWidget()
+        w.clear()
 
-    @pyqtSlot(QString, QString)
-    def output(self, msg, label):
-        self.logte.appendLog(msg, label)
+    def appendLog(self, msg, label, reporoot=None):
+        w = self._findConsoleFor(reporoot)
+        w.appendLog(msg, label)
 
-    @pyqtSlot()
-    def beginSuppressPrompt(self):
-        self.logte.suppressPrompt = True
-
-    @pyqtSlot()
-    def endSuppressPrompt(self):
-        self.logte.suppressPrompt = False
-        self.logte.openPrompt()
-
-    def showEvent(self, event):
-        self.visibilityChanged.emit(True)
+    @pyqtSlot(bool)
+    def _setFocusOnToggleView(self, visible):
+        if visible:
+            w = self._consoles.currentWidget()
+            w.setFocus()
 
     def setVisible(self, visible):
         super(LogDockWidget, self).setVisible(visible)
         if visible:
             self.raise_()
 
-    def hideEvent(self, event):
-        self.visibilityChanged.emit(False)
+    @pyqtSlot(Qt.DockWidgetArea)
+    def _updateTitleBarStyle(self, area):
+        f = self.features()
+        if area & (Qt.TopDockWidgetArea | Qt.BottomDockWidgetArea):
+            f |= QDockWidget.DockWidgetVerticalTitleBar  # saves vertical space
+        else:
+            f &= ~QDockWidget.DockWidgetVerticalTitleBar
+        self.setFeatures(f)

File tortoisehg/hgqt/filectxactions.py

View file
  • Ignore whitespace
         self._itemissubrepo = False
         self._itemisdir = False
 
-        self._nav_dialogs = {}
+        self._nav_dialogs = qtlib.DialogKeeper(FilectxActions._createnavdialog,
+                                               FilectxActions._gennavdialogkey,
+                                               self)
         self._contextmenus = {}
 
         self._actions = {}
     def _navigate(self, dlgclass):
         repo, filename, rev = self._findsubsingle(self._currentfile)
         if filename and len(repo.file(filename)) > 0:
-            fullpath = repo.wjoin(filename)
-            if (dlgclass, fullpath) not in self._nav_dialogs:
-                # dirty hack to pass workbench only if available
-                from tortoisehg.hgqt import workbench  # avoid cyclic dep
-                repoviewer = None
-                if self.parent() and isinstance(self.parent().window(),
-                                                workbench.Workbench):
-                    repoviewer = self.parent().window()
-                dlg = dlgclass(repo, filename, repoviewer=repoviewer)
-                self._nav_dialogs[dlgclass, fullpath] = dlg
-                assert dlg.repo.wjoin(dlg.filename) == fullpath
-                dlg.finished.connect(self._forgetnavdialog)
-                ufname = hglib.tounicode(filename)
-                dlg.setWindowTitle(_('Hg file log viewer - %s') % ufname)
-                dlg.setWindowIcon(qtlib.geticon('hg-log'))
-            dlg = self._nav_dialogs[dlgclass, fullpath]
+            dlg = self._nav_dialogs.open(dlgclass, repo, filename)
             dlg.goto(rev)
-            dlg.show()
-            dlg.raise_()
-            dlg.activateWindow()
 
-    #@pyqtSlot()
-    def _forgetnavdialog(self):
-        dlg = self.sender()
-        dlg.finished.disconnect(self._forgetnavdialog)
-        fullpath = dlg.repo.wjoin(dlg.filename)
-        del self._nav_dialogs[dlg.__class__, fullpath]
+    def _createnavdialog(self, dlgclass, repo, filename):
+        return dlgclass(repo, filename)
+
+    def _gennavdialogkey(self, dlgclass, repo, filename):
+        return dlgclass, repo.wjoin(filename)
 
     def _findsub(self, paths):
         """Find the nearest (sub-)repository for the given paths

File tortoisehg/hgqt/filedialogs.py

View file
  • Ignore whitespace
 class _AbstractFileDialog(QMainWindow):
     finished = pyqtSignal(int)
 
-    def __init__(self, repo, filename, repoviewer=None):
+    def __init__(self, repo, filename):
         QMainWindow.__init__(self)
         self.repo = repo
 
         self.setupUi(self)
-        self.setRepoViewer(repoviewer)
         self._show_rev = None
 
         assert not isinstance(filename, (unicode, QString))
         self.filename = filename
 
+        self.setWindowTitle(_('Hg file log viewer [%s] - %s')
+                            % (repo.displayname, hglib.tounicode(filename)))
+        self.setWindowIcon(qtlib.geticon('hg-log'))
+
         self.createActions()
         self.setupToolbars()
 
         super(_AbstractFileDialog, self).closeEvent(event)
         self.finished.emit(0)  # mimic QDialog exit
 
-    def setRepoViewer(self, repoviewer=None):
-        self.repoviewer = repoviewer
-        if repoviewer:
-            repoviewer.finished.connect(self._clearRepoViewer)
-
-    @pyqtSlot()
-    def _clearRepoViewer(self):
-        self.setRepoViewer(None)
-
     def reload(self):
         'Reload toolbar action handler'
         self.repo.thginvalidate()
         """
         Callback called when a revision is double-clicked in the revisions table
         """
-        if self.repoviewer is None:
-            # prevent recursive import
-            from workbench import Workbench
-            self.repoviewer = Workbench()
-        self.repoviewer.show()
-        self.repoviewer.activateWindow()
-        self.repoviewer.raise_()
-        self.repoviewer.showRepo(hglib.tounicode(self.repo.root))
-        self.repoviewer.goto(self.repo.root, rev)
+        # TODO: implement by using signal-slot if possible
+        from tortoisehg.hgqt import run
+        run.qtrun.showRepoInWorkbench(hglib.tounicode(self.repo.root), rev)
 
 class FileLogDialog(_AbstractFileDialog):
     """
     A dialog showing a revision graph for a file.
     """
-    def __init__(self, repo, filename, repoviewer=None):
-        super(FileLogDialog, self).__init__(repo, filename, repoviewer)
+    def __init__(self, repo, filename):
+        super(FileLogDialog, self).__init__(repo, filename)
         self._readSettings()
         self.menu = None
         self.dualmenu = None
     def modelFilled(self):
         self.repoview.resizeColumns()
         if self._show_rev is not None:
-            index = self.filerevmodel.indexFromRev(self._show_rev)
+            index = self.filerevmodel.indexLinkedFromRev(self._show_rev)
             self._show_rev = None
         elif self.repoview.currentIndex().isValid():
             return  # already set by goto()
         dlg = visdiff.visualdiff(self.repo.ui, self.repo, [], opts)
         if dlg:
             dlg.exec_()
-            dlg.deleteLater()
 
     def onVisualDiffToLocal(self):
         opts = dict(rev=['rev(%d)' % self.selection[0]])
         dlg = visdiff.visualdiff(self.repo.ui, self.repo, [], opts)
         if dlg:
             dlg.exec_()
-            dlg.deleteLater()
 
     def onVisualDiffRevs(self):
         revs = self.selection
         dlg = visdiff.visualdiff(self.repo.ui, self.repo, [], opts)
         if dlg:
             dlg.exec_()
-            dlg.deleteLater()
 
     def onVisualDiffFile(self):
         rev = self.selection[0]
         dlg = visdiff.visualdiff(self.repo.ui, self.repo, paths, opts)
         if dlg:
             dlg.exec_()
-            dlg.deleteLater()
 
     def onVisualDiffFileToLocal(self):
         rev = self.selection[0]
         dlg = visdiff.visualdiff(self.repo.ui, self.repo, paths, opts)
         if dlg:
             dlg.exec_()
-            dlg.deleteLater()
 
     def onVisualDiffFileRevs(self):
         revs = self.selection
         dlg = visdiff.visualdiff(self.repo.ui, self.repo, paths, opts)
         if dlg:
             dlg.exec_()
-            dlg.deleteLater()
 
     def onEditLocal(self):
         filenames = [self.filename]
         self.textView.showMessage.emit(msg)
 
     def goto(self, rev):
-        index = self.filerevmodel.indexFromRev(rev)
+        index = self.filerevmodel.indexLinkedFromRev(rev)
         if index is not None:
             self.repoview.setCurrentIndex(index)
         else:
             self._show_rev = rev
 
+    def showLine(self, line):
+        self.textView.showLine(line - 1)  # fileview should do -1 instead?
+
+    def setFileViewMode(self, mode):
+        self.textView.setMode(mode)
+
+    def setSearchPattern(self, text):
+        self.textView.searchbar.setPattern(text)
+
+    def setSearchCaseInsensitive(self, ignorecase):
+        self.textView.searchbar.setCaseInsensitive(ignorecase)
+
     def reload(self):
         self.repoview.saveSettings()
         super(FileLogDialog, self).reload()
     """
     Qt4 dialog to display diffs between different mercurial revisions of a file.
     """
-    def __init__(self, repo, filename, repoviewer=None):
-        super(FileDiffDialog, self).__init__(repo, filename, repoviewer)
+    def __init__(self, repo, filename):
+        super(FileDiffDialog, self).__init__(repo, filename)
         self._readSettings()
         self.menu = None
 
         self.update_diff(keeppos=otherside[side])
 
     def goto(self, rev):
-        index = self.filerevmodel.indexFromRev(rev)
+        index = self.filerevmodel.indexLinkedFromRev(rev)
         if index is not None:
             if index.row() == 0:
                 index = self.filerevmodel.index(1, 0)
         dlg = visdiff.visualdiff(self.repo.ui, self.repo, [], opts)
         if dlg:
             dlg.exec_()
-            dlg.deleteLater()
 
     def onVisualDiffToLocal(self):
         opts = dict(rev=['rev(%d)' % self.selection[0]])
         dlg = visdiff.visualdiff(self.repo.ui, self.repo, [], opts)
         if dlg:
             dlg.exec_()
-            dlg.deleteLater()
 
     def onVisualDiffFile(self):
         rev = self.selection[0]
         dlg = visdiff.visualdiff(self.repo.ui, self.repo, paths, opts)
         if dlg:
             dlg.exec_()
-            dlg.deleteLater()
 
     def onVisualDiffFileToLocal(self):
         rev = self.selection[0]
         dlg = visdiff.visualdiff(self.repo.ui, self.repo, paths, opts)
         if dlg:
             dlg.exec_()
-            dlg.deleteLater()
 
     def onEditLocal(self):
         filenames = [self.filename]

File tortoisehg/hgqt/filelistview.py

View file
  • Ignore whitespace
     """
 
     fileSelected = pyqtSignal(QString, QString)
-    linkActivated = pyqtSignal(QString)
     clearDisplay = pyqtSignal()
 
     def __init__(self, repo, parent, multiselectable):

File tortoisehg/hgqt/filerevmodel.py

View file
  • Ignore whitespace
 # this program; if not, write to the Free Software Foundation, Inc.,
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
+from mercurial import error
+
 from tortoisehg.hgqt.repomodel import HgRepoListModel, COLUMNHEADERS
 from tortoisehg.hgqt.graph import Graph, filelog_grapher
 from tortoisehg.hgqt.i18n import _
     Model used to manage the list of revisions of a file, in file
     viewer of in diff-file viewer dialogs.
     """
-    filled = pyqtSignal()
 
     _allcolumns = tuple(h[0] for h in FILE_COLUMNHEADERS)
     _allcolnames = dict(FILE_COLUMNHEADERS)
         else:
             self.graph = None
             self.heads = []
+
+    def indexLinkedFromRev(self, rev):
+        """Index for the last changed revision before the specified revision
+
+        This does not follow renames.
+        """
+        # as of Mercurial 2.6, workingfilectx.linkrev() does not work, and
+        # this model has no virtual working-dir revision.
+        if rev is None:
+            rev = '.'
+        try:
+            fctx = self.repo[rev][self.filename]
+        except error.LookupError:
+            return None
+        return self.indexFromRev(fctx.linkrev())

File tortoisehg/hgqt/fileview.py

View file
  • Ignore whitespace
         self.modeToggleGroup.addAction(self.actionDiffMode)
         self.modeToggleGroup.addAction(self.actionFileMode)
         self.modeToggleGroup.addAction(self.actionAnnMode)
-        self.modeToggleGroup.triggered.connect(self.setMode)
+        self.modeToggleGroup.triggered.connect(self._setModeByAction)
 
         # Next/Prev diff (in full file mode)
         self.actionNextDiff = QAction(qtlib.geticon('go-down'),
                                       _('Previous diff (alt+up)'), self)
         self.actionPrevDiff.setShortcut('Alt+Up')
         self.actionPrevDiff.triggered.connect(self.prevDiff)
-        self.setMode(self.actionDiffMode)
+        self._setModeByAction(self.actionDiffMode)
 
         self.actionFirstParent = QAction('1', self)
         self.actionFirstParent.setCheckable(True)
         return True
 
     @pyqtSlot(QAction)
-    def setMode(self, action):
+    def _setModeByAction(self, action):
         'One of the mode toolbar buttons has been toggled'
         mode = action._mode
         self._lostMode = mode
             self.sci.setAnnotationEnabled(mode == AnnMode)
             self.displayFile(self._filename, self._status)
 
+    def setMode(self, mode):
+        """Switch view to DiffMode/FileMode/AnnMode if available for the current
+        content; otherwise it will be switched later"""
+        actionmap = dict((a._mode, a) for a in self.modeToggleGroup.actions())
+        try:
+            action = actionmap[mode]
+        except KeyError:
+            raise ValueError('invalid mode: %r' % mode)
+
+        if action.isEnabled():
+            if not action.isChecked():
+                action.trigger()  # implies _setModeByAction()
+        else:
+            self._lostMode = mode
+
     @pyqtSlot(QAction)
     def setParent(self, action):
         if action.text() == '1':
 
         selection = self.sci.selectedText()
         def sreq(**opts):
-            opts['search'] = True
             return lambda: self.grepRequested.emit(selection, opts)
         def sann():
             self.searchbar.search(selection)
         fctx, line = self.sci._links[line]
         if selection:
             def sreq(**opts):
-                opts['search'] = True
                 return lambda: self.grepRequested.emit(selection, opts)
             def sann():
                 self.searchbar.search(selection)

File tortoisehg/hgqt/graft.py

View file
  • Ignore whitespace
                                         labels=labels, parent=self):
                 return
         super(GraftDialog, self).reject()
-
-def run(ui, *revs, **opts):
-    from tortoisehg.util import paths
-    repo = thgrepo.repository(ui, path=paths.find_root())
-
-    revs = list(revs)
-    revs.extend(opts['rev'])
-
-    if os.path.exists(repo.join('graftstate')):
-        qtlib.InfoMsgBox(_('Graft already in progress'),
-                          _('Resuming graft already in progress'))
-    elif not revs:
-        qtlib.ErrorMsgBox(_('Abort'),
-                          _('You must provide revisions to graft'))
-        import sys; sys.exit()
-    return GraftDialog(repo, None, source=revs)

File tortoisehg/hgqt/graph.py

View file
  • Ignore whitespace
 import os
 import itertools
 
-from mercurial import repoview, util, error
+from mercurial import repoview
 
 LINE_TYPE_PARENT = 0
 LINE_TYPE_GRAFT = 1
       - current revision
       - column of the current node in the set of ongoing edges
       - color of the node (?)
-      - lines; a list of (col, next_col, color_no, line_type) defining
-        the edges between the current row and the next row
+      - lines: a list of (col, next_col, color_no, line_type, children, parent)
+          children: tuple of revs which connected to top of this line.
+                    (or current rev if node is on the line.)
+          parent:   rev which connected to bottom of this line.
+        defining the edges between the current row and the next row
       - parent revisions of current revision
     """
 
 
     curr_rev = start_rev
     revs = []
+    children = [()]
     links = [] # smallest link type that applies
-    rev_color = {}
-    nextcolor = 0
 
     if opts.get('allparents') or not branch:
         def getparents(ctx):
-            return [x.rev() for x in ctx.parents() if x]
+            return [x for x in ctx.parents() if x]
     else:
         def getparents(ctx):
-            return [x.rev() for x in ctx.parents() \
+            return [x for x in ctx.parents()
                     if x and x.branch() == branch]
 
+    rev_color = RevColorPalette(getparents)
+
     while curr_rev is None or curr_rev >= stop_rev:
         if hidden(curr_rev):
             curr_rev -= 1
                 continue
             revs.append(curr_rev)
             links.append(LINE_TYPE_PARENT)
-            rev_color[curr_rev] = curcolor = nextcolor
-            nextcolor += 1
-            p_revs = getparents(ctx)
-            while p_revs:
-                rev0 = p_revs[0]
-                if rev0 < stop_rev or rev0 in rev_color:
-                    break
-                rev_color[rev0] = curcolor
-                p_revs = getparents(repo[rev0])
+            children.append(())
+            rev_color.addheadctx(ctx)
         curcolor = rev_color[curr_rev]
         rev_index = revs.index(curr_rev)
         next_revs = revs[:]
         next_links = links[:]
+        next_children = children[:]
 
         # Add parents to next_revs.
-        parents = [(p,LINE_TYPE_PARENT) for p in getparents(ctx) if not hidden(p)]
+        parents = [(p.rev(), LINE_TYPE_PARENT) for p in getparents(ctx)
+                   if not hidden(p.rev())]
         if 'source' in ctx.extra():
             src_rev_str = ctx.extra()['source']
             if src_rev_str in repo:
                     parents.append((src_rev, LINE_TYPE_GRAFT))
         parents_to_add = []
         links_to_add = []
+        children_to_add = []
         if len(parents) > 1:
             preferred_color = None
         else:
             if parent not in next_revs:
                 parents_to_add.append(parent)
                 links_to_add.append(link_type)
+                children_to_add.append((curr_rev,))
                 if parent not in rev_color:
-                    if preferred_color:
-                        rev_color[parent] = preferred_color
-                        preferred_color = None
-                    else:
-                        rev_color[parent] = nextcolor
-                        nextcolor += 1
+                    rev_color.assigncolor(parent, preferred_color)
+                    preferred_color = None
             else:
                 # Merging lines should have the most solid style
                 #  (= lowest style value)
                 i = next_revs.index(parent)
                 next_links[i] = min(next_links[i], link_type)
+                next_children[i] += (curr_rev,)
             preferred_color = None
 
         # parents_to_add.sort()
         next_revs[rev_index:rev_index + 1] = parents_to_add
         next_links[rev_index:rev_index + 1] = links_to_add
+        next_children[rev_index:rev_index + 1] = children_to_add
 
         lines = []
         for i, rev in enumerate(revs):
             if rev in next_revs:
                 color = rev_color[rev]
-                lines.append( (i, next_revs.index(rev), color, links[i]) )
+                lines.append((i, next_revs.index(rev), color, links[i],
+                              children[i], rev))
             elif rev == curr_rev:
                 for parent, link_type in parents:
                     color = rev_color[parent]
-                    lines.append( (i, next_revs.index(parent), color, link_type) )
+                    lines.append((i, next_revs.index(parent), color, link_type,
+                                  (curr_rev,), parent))
 
         yield GraphNode(curr_rev, rev_index, curcolor, lines, parents)
         revs = next_revs
         links = next_links
+        children = next_children
         if curr_rev is None:
             curr_rev = len(repo)
         else:
     heads.remove(rev)
 
     revs = []
+    children = [()]
     rev_color = {}
     nextcolor = 0
     _paths = {}
         if rev not in revs:
             revs.append(rev)
             rev_color[rev] = nextcolor ; nextcolor += 1
+            children.append(())
         curcolor = rev_color[rev]
         index = revs.index(rev)
         next_revs = revs[:]
+        next_children = children[:]
 
         # Add parents to next_revs
         fctx = repo.filectx(_paths.get(rev, path), changeid=rev)
         for pfctx in fctx.parents():
             _paths[pfctx.rev()] = pfctx.path()
-        parents = [pfctx.rev() for pfctx in fctx.parents()]# if f.path() == path]
+        parents = [pfctx.rev() for pfctx in fctx.parents()]
+                   # if f.path() == path]
         parents_to_add = []
+        children_to_add = []
         for parent in parents:
             if parent not in next_revs:
                 parents_to_add.append(parent)
+                children_to_add.append((rev,))
                 if len(parents) > 1:
                     rev_color[parent] = nextcolor ; nextcolor += 1
                 else:
                     rev_color[parent] = curcolor
         parents_to_add.sort()
         next_revs[index:index + 1] = parents_to_add
+        next_children[index:index + 1] = children_to_add
 
         lines = []
         for i, nrev in enumerate(revs):
             if nrev in next_revs:
                 color = rev_color[nrev]
-                lines.append( (i, next_revs.index(nrev), color, LINE_TYPE_PARENT) )
+                lines.append((i, next_revs.index(nrev), color, LINE_TYPE_PARENT,
+                              children[i], nrev))
             elif nrev == rev:
                 for parent in parents:
                     color = rev_color[parent]
-                    lines.append( (i, next_revs.index(parent), color, LINE_TYPE_PARENT) )
+                    lines.append((i, next_revs.index(parent), color,
+                                  LINE_TYPE_PARENT, (rev,), parent))
 
         pcrevs = [pfc.rev() for pfc in fctx.parents()]
         yield GraphNode(fctx.rev(), index, curcolor, lines, pcrevs,
                         extra=[_paths.get(fctx.rev(), path)])
         revs = next_revs
+        children = next_children
 
         if revs:
             rev = max(revs)
     for patchname in reversed(repo.thgmqunappliedpatches):
         yield GraphNode(patchname, 0, "", [], [])
 
+class RevColorPalette(object):
+    """Assign node and line colors for each revision"""
+
+    def __init__(self, getparents):
+        self._getparents = getparents
+        self._pendingheads = []
+        self._knowncolors = {}
+        self._nextcolor = 0
+
+    def addheadctx(self, ctx):
+        color = self.assigncolor(ctx.rev())
+        p_ctxs = self._getparents(ctx)
+        self._pendingheads.append((p_ctxs, color))
+
+    def _fillpendingheads(self, stoprev):
+        if stoprev is None:
+            return  # avoid filling everything (int_rev < None is False)
+
+        nextpendingheads = []
+        for p_ctxs, color in self._pendingheads:
+            pending = self._fillancestors(p_ctxs, color, stoprev)
+            if pending:
+                nextpendingheads.append((pending, color))
+        self._pendingheads = nextpendingheads
+
+    def _fillancestors(self, p_ctxs, curcolor, stoprev):
+        while p_ctxs:
+            ctx0 = p_ctxs[0]
+            rev0 = ctx0.rev()
+            if rev0 < stoprev:
+                return p_ctxs
+            if rev0 in self._knowncolors:
+                return
+            self._knowncolors[rev0] = curcolor
+            p_ctxs = self._getparents(ctx0)
+
+    def assigncolor(self, rev, color=None):
+        self._fillpendingheads(rev)
+        if color is None:
+            color = self._nextcolor
+            self._nextcolor += 1
+        self._knowncolors[rev] = color
+        return color
+
+    def __getitem__(self, rev):
+        self._fillpendingheads(rev)
+        return self._knowncolors[rev]
+
+    def __contains__(self, rev):
+        self._fillpendingheads(rev)
+        return rev in self._knowncolors
+
 class GraphNode(object):
     """
     Simple class to encapsulate a hg node in the revision graph. Does

File tortoisehg/hgqt/grep.py

View file
  • Ignore whitespace
 from mercurial import ui, hg, error, commands, match, util, subrepo
 
 from tortoisehg.hgqt import htmlui, visdiff, qtlib, htmldelegate, thgrepo, cmdui, settings
+from tortoisehg.hgqt import filedialogs, fileview
 from tortoisehg.util import paths, hglib, thread2
 from tortoisehg.hgqt.i18n import _
 
         QWidget.__init__(self, parent)
 
         self.thread = None
-        self.setWindowIcon(qtlib.geticon('view-filter'))
 
         mainvbox = QVBoxLayout()
         mainvbox.setSpacing(6)
         revision.toggled.connect(revisiontoggled)
         history.toggled.connect(singlematch.setDisabled)
         revle.setEnabled(False)
-        revle.returnPressed.connect(self.searchActivated)
-        excle.returnPressed.connect(self.searchActivated)
-        incle.returnPressed.connect(self.searchActivated)
-        bt.clicked.connect(self.searchActivated)
+        revle.returnPressed.connect(self.runSearch)
+        excle.returnPressed.connect(self.runSearch)
+        incle.returnPressed.connect(self.runSearch)
+        bt.clicked.connect(self.runSearch)
 
         def updateRecurse(checked):
             try:
         tv.setColumnHidden(COL_REVISION, True)
         tv.setColumnHidden(COL_USER, True)