/tortoisehg/hgqt/repowidget.py
https://bitbucket.org/tortoisehg/hgtk/ · Python · 1130 lines · 1080 code · 40 blank · 10 comment · 12 complexity · 790e5ff1b2091dbdd18ffc212005002e MD5 · raw file
- # repowidget.py - TortoiseHg repository widget
- #
- # Copyright (C) 2007-2010 Logilab. All rights reserved.
- # Copyright (C) 2010 Adrian Buehlmann <adrian@cadifra.com>
- #
- # This software may be used and distributed according to the terms
- # of the GNU General Public License, incorporated herein by reference.
- import binascii
- import os
- from mercurial import util, revset, error
- from tortoisehg.util import shlib, hglib
- from tortoisehg.hgqt.i18n import _
- from tortoisehg.hgqt.qtlib import geticon, getfont, QuestionMsgBox, InfoMsgBox
- from tortoisehg.hgqt.qtlib import CustomPrompt, SharedWidget, DemandWidget
- from tortoisehg.hgqt.repomodel import HgRepoListModel
- from tortoisehg.hgqt import cmdui, update, tag, backout, merge, visdiff
- from tortoisehg.hgqt import archive, thgimport, thgstrip, run, purge, bookmark
- from tortoisehg.hgqt import bisect, rebase, resolve, thgrepo, compress, mq
- from tortoisehg.hgqt import qdelete, qreorder, qrename, qfold, shelve
- from tortoisehg.hgqt.repofilter import RepoFilterBar
- from tortoisehg.hgqt.repoview import HgRepoView
- from tortoisehg.hgqt.revdetails import RevDetailsWidget
- from tortoisehg.hgqt.commit import CommitWidget
- from tortoisehg.hgqt.manifestdialog import ManifestTaskWidget
- from tortoisehg.hgqt.sync import SyncWidget
- from tortoisehg.hgqt.grep import SearchWidget
- from tortoisehg.hgqt.quickbar import GotoQuickBar
- from tortoisehg.hgqt.pbranch import PatchBranchWidget
- from PyQt4.QtCore import *
- from PyQt4.QtGui import *
- class RepoWidget(QWidget):
- showMessageSignal = pyqtSignal(QString)
- closeSelfSignal = pyqtSignal(QWidget)
- output = pyqtSignal(QString, QString)
- progress = pyqtSignal(QString, object, QString, QString, object)
- makeLogVisible = pyqtSignal(bool)
- revisionSelected = pyqtSignal(object)
- titleChanged = pyqtSignal(unicode)
- """Emitted when changed the expected title for the RepoWidget tab"""
- repoLinkClicked = pyqtSignal(unicode)
- """Emitted when clicked a link to open repository"""
- singlecmenu = None
- unappcmenu = None
- paircmenu = None
- multicmenu = None
- def __init__(self, repo, parent=None):
- QWidget.__init__(self, parent, acceptDrops=True)
- self.repo = repo
- repo.repositoryChanged.connect(self.repositoryChanged)
- repo.repositoryDestroyed.connect(self.repositoryDestroyed)
- repo.configChanged.connect(self.configChanged)
- self.revsetfilter = False
- self.branch = ''
- self.bundle = None
- self.revset = set()
- self.namedTabs = {}
- if repo.parents()[0].rev() == -1:
- self._reload_rev = 'tip'
- else:
- self._reload_rev = '.'
- self.currentMessage = ''
- self.runner = None
- self.dirty = False
- self.setupUi()
- self.createActions()
- self.restoreSettings()
- self.setupModels()
- def setupUi(self):
- SP = QSizePolicy
- self.repotabs_splitter = QSplitter(orientation=Qt.Vertical)
- self.setLayout(QVBoxLayout())
- self.layout().setContentsMargins(0, 0, 0, 0)
- self.layout().setSpacing(0)
- hbox = QHBoxLayout()
- hbox.setContentsMargins(0, 0, 0, 0)
- hbox.setSpacing(0)
- self.layout().addLayout(hbox)
- self.gototb = tb = GotoQuickBar(self)
- tb.setObjectName('gototb')
- tb.gotoSignal.connect(self.goto)
- hbox.addWidget(tb)
- self.bundleAccept = b = QPushButton(_('Accept'))
- b.setShown(False)
- b.setToolTip(_('Pull incoming changesets into your repository'))
- b.clicked.connect(self.acceptBundle)
- hbox.addWidget(b)
- self.bundleReject = b = QPushButton(_('Reject'))
- b.setToolTip(_('Reject incoming changesets'))
- b.clicked.connect(self.rejectBundle)
- b.setShown(False)
- hbox.addWidget(b)
- self.filterbar = RepoFilterBar(self.repo, self)
- self.filterbar.branchChanged.connect(self.setBranch)
- self.filterbar.progress.connect(self.progress)
- self.filterbar.showMessage.connect(self.showMessage)
- self.filterbar.revisionSet.connect(self.setRevisionSet)
- self.filterbar.clearSet.connect(self.clearSet)
- self.filterbar.filterToggled.connect(self.filterToggled)
- hbox.addWidget(self.filterbar)
- self.filterbar.hide()
- self.revsetfilter = self.filterbar.filtercb.isChecked()
- self.layout().addWidget(self.repotabs_splitter)
- self.repoview = view = HgRepoView(self.repo, self)
- view.revisionClicked.connect(self.onRevisionClicked)
- view.revisionSelected.connect(self.onRevisionSelected)
- view.revisionAltClicked.connect(self.onRevisionSelected)
- view.revisionActivated.connect(self.revision_activated)
- view.showMessage.connect(self.showMessage)
- view.menuRequested.connect(self.viewMenuRequest)
- sp = SP(SP.Expanding, SP.Expanding)
- sp.setHorizontalStretch(0)
- sp.setVerticalStretch(1)
- sp.setHeightForWidth(self.repoview.sizePolicy().hasHeightForWidth())
- view.setSizePolicy(sp)
- view.setFrameShape(QFrame.StyledPanel)
- self.repotabs_splitter.addWidget(self.repoview)
- self.repotabs_splitter.setCollapsible(0, False)
- self.repotabs_splitter.setStretchFactor(0, 1)
- self.taskTabsWidget = tt = QTabWidget()
- self.repotabs_splitter.addWidget(self.taskTabsWidget)
- self.repotabs_splitter.setStretchFactor(1, 1)
- tt.setDocumentMode(True)
- self.updateTaskTabs()
- self.revDetailsWidget = w = RevDetailsWidget(self.repo)
- w.linkActivated.connect(self._openLink)
- w.revisionSelected.connect(self.repoview.goto)
- w.grepRequested.connect(self.grep)
- w.showMessage.connect(self.showMessage)
- self.logTabIndex = idx = tt.addTab(w, geticon('log'), '')
- tt.setTabToolTip(idx, _("Revision details"))
- self.commitDemand = w = DemandWidget(self.createCommitWidget)
- self.commitTabIndex = idx = tt.addTab(w, geticon('Checkmark'), '')
- tt.setTabToolTip(idx, _("Commit"))
- self.manifestDemand = w = DemandWidget(self.createManifestWidget)
- self.manifestTabIndex = idx = tt.addTab(w, geticon('annotate'), '')
- tt.setTabToolTip(idx, _('Manifest'))
- self.grepDemand = w = DemandWidget(self.createGrepWidget)
- self.grepTabIndex = idx = tt.addTab(w, geticon('repobrowse'), '')
- tt.setTabToolTip(idx, _("Search"))
- self.syncDemand = w = DemandWidget(self.createSyncWidget)
- self.syncTabIndex = idx = tt.addTab(w, geticon('view-refresh'), '')
- tt.setTabToolTip(idx, _("Synchronize"))
- self.mqDemand = w = DemandWidget(self.createMQWidget)
- if 'mq' in self.repo.extensions():
- self.mqTabIndex = idx = tt.addTab(w, geticon('mq'), '')
- tt.setTabToolTip(idx, _("Patch Queue"))
- self.namedTabs['mq'] = idx
- else:
- self.mqTabIndex = -1
- self.pbranchDemand = w = DemandWidget(self.createPatchBranchWidget)
- if 'pbranch' in self.repo.extensions():
- self.pbranchTabIndex = idx = tt.addTab(w, geticon('branch'), '')
- tt.setTabToolTip(idx, _("Patch Branch"))
- self.namedTabs['pbranch'] = idx
- else:
- self.pbranchTabIndex = -1
- def switchToNamedTaskTab(self, tabname):
- if tabname in self.namedTabs:
- idx = self.namedTabs[tabname]
- self.taskTabsWidget.setCurrentIndex(idx)
- def title(self):
- """Returns the expected title for this widget [unicode]"""
- if self.bundle:
- return _('%s <incoming>') % self.repo.shortname
- elif self.branch:
- return '%s [%s]' % (self.repo.shortname, self.branch)
- else:
- return self.repo.shortname
- @pyqtSlot()
- def toggleFilterBar(self):
- """Toggle display repowidget filter bar"""
- vis = self.filterbar.isVisible()
- self.filterbar.setVisible(not vis)
- @pyqtSlot()
- def toggleGotoBar(self):
- """Toggle display repowidget goto bar"""
- vis = self.gototb.isVisible()
- self.gototb.setVisible(not vis)
- @pyqtSlot(unicode)
- def _openLink(self, link):
- link = unicode(link)
- handlers = {'cset': self.goto,
- 'subrepo': self.repoLinkClicked.emit,
- 'shelve' : self.shelve}
- if ':' in link:
- scheme, param = link.split(':', 1)
- hdr = handlers.get(scheme)
- if hdr:
- return hdr(param)
- QDesktopServices.openUrl(QUrl(link))
- def getCommitWidget(self):
- return getattr(self.repo, '_commitwidget', None) # TODO: ugly
- def createCommitWidget(self):
- cw = self.getCommitWidget()
- if not cw:
- pats = {}
- opts = {}
- b = QPushButton(_('Commit'))
- b.setAutoDefault(True)
- f = b.font()
- f.setWeight(QFont.Bold)
- b.setFont(f)
- cw = CommitWidget(pats, opts, self.repo.root, True, self)
- cw.buttonHBox.addWidget(b)
- cw.commitButtonEnable.connect(b.setEnabled)
- b.clicked.connect(cw.commit)
- cw.loadSettings(QSettings(), 'workbench')
- QTimer.singleShot(0, cw.reload)
- self.repo._commitwidget = cw
- cw = SharedWidget(cw)
- cw.output.connect(self.output)
- cw.progress.connect(self.progress)
- cw.makeLogVisible.connect(self.makeLogVisible)
- cw.linkActivated.connect(self._openLink)
- cw.showMessage.connect(self.showMessage)
- return cw
- def createManifestWidget(self):
- if isinstance(self.rev, basestring):
- rev = None
- else:
- rev = self.rev
- w = ManifestTaskWidget(self.repo, rev, self)
- w.loadSettings(QSettings(), 'workbench')
- w.revChanged.connect(self.repoview.goto)
- w.revisionHint.connect(self.showMessage)
- w.grepRequested.connect(self.grep)
- return w
- def createSyncWidget(self):
- sw = getattr(self.repo, '_syncwidget', None) # TODO: ugly
- if not sw:
- sw = SyncWidget(self.repo, True, self)
- self.repo._syncwidget = sw
- sw = SharedWidget(sw)
- sw.output.connect(self.output)
- sw.progress.connect(self.progress)
- sw.makeLogVisible.connect(self.makeLogVisible)
- sw.outgoingNodes.connect(self.setOutgoingNodes)
- sw.showMessage.connect(self.showMessage)
- sw.incomingBundle.connect(self.setBundle)
- sw.refreshTargets(self.rev)
- return sw
- @pyqtSlot(QString)
- def setBundle(self, bfile):
- self.bundle = unicode(bfile)
- oldlen = len(self.repo)
- self.repo = thgrepo.repository(self.repo.ui, self.repo.root,
- bundle=self.bundle)
- self.repoview.setRepo(self.repo)
- self.revDetailsWidget.setRepo(self.repo)
- self.bundleAccept.setHidden(False)
- self.bundleReject.setHidden(False)
- self.filterbar.revsetle.setText('incoming()')
- self.filterbar.setEnabled(False)
- self.filterbar.show()
- self.titleChanged.emit(self.title())
- newlen = len(self.repo)
- self.revset = [self.repo[n].node() for n in range(oldlen, newlen)]
- self.repomodel.revset = self.revset
- self.reload()
- def clearBundle(self):
- self.bundleAccept.setHidden(True)
- self.bundleReject.setHidden(True)
- self.filterbar.setEnabled(True)
- self.filterbar.revsetle.setText('')
- self.revset = []
- self.repomodel.revset = self.revset
- self.bundle = None
- self.titleChanged.emit(self.title())
- self.repo = thgrepo.repository(self.repo.ui, self.repo.root)
- self.repoview.setRepo(self.repo)
- self.revDetailsWidget.setRepo(self.repo)
- def acceptBundle(self):
- self.taskTabsWidget.setCurrentIndex(self.syncTabIndex)
- self.syncDemand.pullBundle(self.bundle, None)
- self.clearBundle()
- def rejectBundle(self):
- self.clearBundle()
- self.reload()
- def clearSet(self):
- self.revset = []
- if self.revsetfilter:
- self.reload()
- else:
- self.repomodel.revset = []
- self.refresh()
- def setRevisionSet(self, nodes):
- self.revset = [self.repo[n].node() for n in nodes]
- if self.revsetfilter:
- self.reload()
- else:
- self.repomodel.revset = self.revset
- self.refresh()
- @pyqtSlot(bool)
- def filterToggled(self, checked):
- self.revsetfilter = checked
- if self.revset:
- self.repomodel.filterbyrevset = checked
- self.reload()
- def setOutgoingNodes(self, nodes):
- self.filterbar.revsetle.setText('outgoing()')
- self.filterbar.show()
- self.setRevisionSet(nodes)
- def createGrepWidget(self):
- upats = {}
- gw = SearchWidget(upats, self.repo, self)
- gw.setRevision(self.repoview.current_rev)
- gw.showMessage.connect(self.showMessage)
- gw.progress.connect(self.progress)
- gw.revisionSelected.connect(self.goto)
- return gw
- def createMQWidget(self):
- mqw = mq.MQWidget(self.repo, self)
- mqw.output.connect(self.output)
- mqw.progress.connect(self.progress)
- mqw.makeLogVisible.connect(self.makeLogVisible)
- mqw.showMessage.connect(self.showMessage)
- return mqw
- def createPatchBranchWidget(self):
- pbw = PatchBranchWidget(self.repo, parent=self)
- pbw.output.connect(self.output)
- pbw.progress.connect(self.progress)
- pbw.makeLogVisible.connect(self.makeLogVisible)
- return pbw
- def reponame(self):
- return self.repo.shortname
- @property
- def rev(self):
- """Returns the current active revision"""
- return self.repoview.current_rev
- def showMessage(self, msg):
- self.currentMessage = msg
- if self.isVisible():
- self.showMessageSignal.emit(msg)
- def showEvent(self, event):
- QWidget.showEvent(self, event)
- self.showMessageSignal.emit(self.currentMessage)
- if self.dirty:
- print 'page was dirty, reloading...'
- self.reload()
- self.dirty = False
- def createActions(self):
- QShortcut(QKeySequence('CTRL+P'), self, self.gotoParent)
- def dragEnterEvent(self, event):
- paths = [unicode(u.toLocalFile()) for u in event.mimeData().urls()]
- if util.any(os.path.isfile(p) for p in paths):
- event.setDropAction(Qt.CopyAction)
- event.accept()
- def dropEvent(self, event):
- paths = [unicode(u.toLocalFile()) for u in event.mimeData().urls()]
- filepaths = [p for p in paths if os.path.isfile(p)]
- if filepaths:
- self.thgimport(filepaths)
- event.setDropAction(Qt.CopyAction)
- event.accept()
- ## Begin Workbench event forwards
- def back(self):
- self.repoview.back()
- def forward(self):
- self.repoview.forward()
- def bisect(self):
- dlg = bisect.BisectDialog(self.repo, {}, self)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- def resolve(self):
- dlg = resolve.ResolveDialog(self.repo, self)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- def thgimport(self, paths=None):
- dlg = thgimport.ImportDialog(repo=self.repo, parent=self)
- dlg.finished.connect(dlg.deleteLater)
- if paths:
- dlg.setfilepaths(paths)
- dlg.exec_()
- def shelve(self, arg=None):
- dlg = shelve.ShelveDialog(self.repo, self)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- def verify(self):
- cmdline = ['--repository', self.repo.root, 'verify', '--verbose']
- dlg = cmdui.Dialog(cmdline, self)
- dlg.exec_()
- def recover(self):
- cmdline = ['--repository', self.repo.root, 'recover', '--verbose']
- dlg = cmdui.Dialog(cmdline, self)
- dlg.exec_()
- def rollback(self):
- def read_undo():
- if os.path.exists(self.repo.sjoin('undo')):
- try:
- args = self.repo.opener('undo.desc', 'r').read().splitlines()
- if args[1] != 'commit':
- return None
- return args[1], int(args[0])
- except (IOError, IndexError, ValueError):
- pass
- return None
- data = read_undo()
- if data is None:
- InfoMsgBox(_('No transaction available'),
- _('There is no rollback transaction available'))
- return
- elif data[0] == 'commit':
- if not QuestionMsgBox(_('Undo last commit?'),
- _('Undo most recent commit (%d), preserving file changes?') %
- data[1]):
- return
- else:
- if not QuestionMsgBox(_('Undo last transaction?'),
- _('Rollback to revision %d (undo %s)?') %
- (data[1]-1, data[0])):
- return
- try:
- rev = self.repo['.'].rev()
- except Exception, e:
- InfoMsgBox(_('Repository Error'),
- _('Unable to determine working copy revision\n') +
- hglib.tounicode(e))
- return
- if rev >= data[1] and not QuestionMsgBox(
- _('Remove current working revision?'),
- _('Your current working revision (%d) will be removed '
- 'by this rollback, leaving uncommitted changes.\n '
- 'Continue?' % rev)):
- return
- cmdline = ['rollback', '--repository', self.repo.root, '--verbose']
- self.runCommand(_('Rollback - TortoiseHg'), cmdline)
- def purge(self):
- dlg = purge.PurgeDialog(self.repo, self)
- dlg.setWindowFlags(Qt.Sheet)
- dlg.setWindowModality(Qt.WindowModal)
- dlg.showMessage.connect(self.showMessage)
- dlg.progress.connect(self.progress)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- ## End workbench event forwards
- @pyqtSlot(unicode, dict)
- def grep(self, pattern='', opts={}):
- """Open grep task tab"""
- opts = dict((str(k), str(v)) for k, v in opts.iteritems())
- self.taskTabsWidget.setCurrentIndex(self.grepTabIndex)
- self.grepDemand.setSearch(pattern, **opts)
- def setupModels(self):
- # Filter revision set in case revisions were removed
- self.revset = [r for r in self.revset if r in self.repo]
- self.repomodel = HgRepoListModel(self.repo, self.branch, self.revset,
- self.revsetfilter, self)
- self.repomodel.filled.connect(self.modelFilled)
- self.repomodel.loaded.connect(self.modelLoaded)
- self.repomodel.showMessage.connect(self.showMessage)
- self.repoview.setModel(self.repomodel)
- self.gototb.setCompletionKeys(self.repo.tags().keys())
- def modelFilled(self):
- 'initial batch of revisions loaded'
- self.repoview.resizeColumns()
- self.repoview.goto(self._reload_rev) # emits revision_selected
- self.revDetailsWidget.finishReload()
- def modelLoaded(self):
- 'all revisions loaded (graph generator completed)'
- # Perhaps we can update a GUI element later, to indicate full load
- pass
- def onRevisionClicked(self, rev):
- 'User clicked on a repoview row'
- tw = self.taskTabsWidget
- if rev is None:
- # Clicking on working copy switches to commit tab
- tw.setCurrentIndex(self.commitTabIndex)
- else:
- # Clicking on a normal revision switches from commit tab
- tw.setCurrentIndex(self.logTabIndex)
- def onRevisionSelected(self, rev):
- 'View selection changed, could be a reload'
- if self.repomodel.graph is None:
- return
- if type(rev) != str: # unapplied patch
- self.manifestDemand.forward('setRev', rev)
- self.grepDemand.forward('setRevision', rev)
- self.syncDemand.forward('refreshTargets', rev)
- self.revDetailsWidget.revision_selected(rev)
- self.revisionSelected.emit(rev)
- def gotoParent(self):
- self.repoview.clearSelection()
- self.goto('.')
- def goto(self, rev):
- self._reload_rev = rev
- self.repoview.goto(rev)
- def revision_activated(self, rev=None):
- rev = rev or self.rev
- if isinstance(rev, basestring): # unapplied patch
- return
- dlg = visdiff.visualdiff(self.repo.ui, self.repo, [], {'change':rev})
- if dlg:
- dlg.exec_()
- def reload(self):
- 'Initiate a refresh of the repo model, rebuild graph'
- self.repo.thginvalidate()
- self.rebuildGraph()
- self.filterbar.refresh()
- if self.taskTabsWidget.currentIndex() == self.commitTabIndex:
- self.commitDemand.forward('reload')
- def rebuildGraph(self):
- self.showMessage('')
- if self.rev is None or len(self.repo) > self.rev:
- self._reload_rev = self.rev
- else:
- self._reload_rev = 'tip'
- self.setupModels()
- self.revDetailsWidget.record()
- def reloadTaskTab(self):
- tti = self.taskTabsWidget.currentIndex()
- if tti == self.logTabIndex:
- ttw = self.revDetailsWidget
- elif tti == self.commitTabIndex:
- ttw = self.commitDemand.get()
- elif tti == self.manifestTabIndex:
- ttw = self.manifestDemand.get()
- elif tti == self.syncTabIndex:
- ttw = self.syncDemand.get()
- elif tti == self.grepTabIndex:
- ttw = self.grepDemand.get()
- elif tti == self.pbranchTabIndex:
- ttw = self.pbranchDemand.get()
- elif tti == self.mqTabIndex:
- ttw = self.mqDemand.get()
- if ttw:
- ttw.reload()
- def refresh(self):
- 'Refresh the repo model view, clear cached data'
- self.repo.thginvalidate()
- self.repomodel.invalidate()
- self.revDetailsWidget.reload()
- self.filterbar.refresh()
- def repositoryDestroyed(self):
- 'Repository has detected itself to be deleted'
- self.closeSelfSignal.emit(self)
- def repositoryChanged(self):
- 'Repository has detected a changelog / dirstate change'
- if self.isVisible():
- try:
- self.rebuildGraph()
- except (error.RevlogError, error.RepoError), e:
- self.showMessage(hglib.tounicode(str(e)))
- self.repomodel = HgRepoListModel(None, None, None, False, self)
- self.repoview.setModel(self.repomodel)
- else:
- self.dirty = True
- def configChanged(self):
- 'Repository is reporting its config files have changed'
- self.repomodel.invalidate()
- self.revDetailsWidget.reload()
- self.titleChanged.emit(self.title())
- self.updateTaskTabs()
- def updateTaskTabs(self):
- val = self.repo.ui.config('tortoisehg', 'tasktabs', 'off').lower()
- if val == 'east':
- self.taskTabsWidget.setTabPosition(QTabWidget.East)
- elif val == 'west':
- self.taskTabsWidget.setTabPosition(QTabWidget.West)
- else:
- self.taskTabsWidget.tabBar().hide()
- @pyqtSlot(unicode, bool)
- def setBranch(self, branch, allparents=True):
- 'Change the branch filter'
- self.branch = branch
- self.repomodel.setBranch(branch=branch, allparents=allparents)
- self.titleChanged.emit(self.title())
- ##
- ## Workbench methods
- ##
- def canGoBack(self):
- return self.repoview.canGoBack()
- def canGoForward(self):
- return self.repoview.canGoForward()
- def storeSettings(self):
- self.revDetailsWidget.storeSettings()
- s = QSettings()
- repoid = str(self.repo[0])
- s.setValue('repowidget/splitter-'+repoid,
- self.repotabs_splitter.saveState())
- def restoreSettings(self):
- self.revDetailsWidget.restoreSettings()
- s = QSettings()
- repoid = str(self.repo[0])
- self.repotabs_splitter.restoreState(
- s.value('repowidget/splitter-'+repoid).toByteArray())
- def okToContinue(self):
- return self.commitDemand.forward('canExit', default=True) and \
- self.syncDemand.forward('canExit', default=True) and \
- self.mqDemand.forward('canExit', default=True)
- def closeRepoWidget(self):
- '''returns False if close should be aborted'''
- if not self.okToContinue():
- return False
- if self.isVisible():
- # assuming here that there is at most one RepoWidget visible
- self.storeSettings()
- self.revDetailsWidget.storeSettings()
- s = QSettings()
- self.commitDemand.forward('saveSettings', s, 'workbench')
- self.manifestDemand.forward('saveSettings', s, 'workbench')
- self.filterbar.storeConfigs(s)
- return True
- def incoming(self):
- self.taskTabsWidget.setCurrentIndex(self.syncTabIndex)
- self.syncDemand.get().incoming()
- def pull(self):
- self.taskTabsWidget.setCurrentIndex(self.syncTabIndex)
- self.syncDemand.get().pull()
- def outgoing(self):
- self.taskTabsWidget.setCurrentIndex(self.syncTabIndex)
- self.syncDemand.get().outgoing()
- def push(self):
- self.taskTabsWidget.setCurrentIndex(self.syncTabIndex)
- self.syncDemand.get().push()
- def qpush(self):
- """QPush a patch from MQ"""
- self.taskTabsWidget.setCurrentIndex(self.mqTabIndex)
- self.mqDemand.get().onPush()
- def qpop(self):
- """QPop a patch from MQ"""
- self.taskTabsWidget.setCurrentIndex(self.mqTabIndex)
- self.mqDemand.get().onPop()
- ##
- ## Repoview context menu
- ##
- def viewMenuRequest(self, point, selection):
- 'User requested a context menu in repo view widget'
- # selection is a list of the currently selected revisions.
- # Integers for changelog revisions, None for the working copy,
- # or strings for unapplied patches.
- if len(selection) == 0:
- return
- allunapp = False
- if 'mq' in self.repo.extensions():
- for rev in selection:
- if not self.repo.changectx(rev).thgmqunappliedpatch():
- break
- else:
- allunapp = True
- if allunapp:
- self.unnapliedPatchMenu(point, selection)
- elif len(selection) == 1:
- self.singleSelectionMenu(point, selection)
- elif len(selection) == 2:
- self.doubleSelectionMenu(point, selection)
- else:
- self.multipleSelectionMenu(point, selection)
- def unnapliedPatchMenu(self, point, selection):
- def qdeleteact():
- """Delete unapplied patch(es)"""
- dlg = qdelete.QDeleteDialog(self.repo, self.menuselection, self)
- dlg.finished.connect(dlg.deleteLater)
- dlg.output.connect(self.output)
- dlg.makeLogVisible.connect(self.makeLogVisible)
- dlg.exec_()
- def qreorderact():
- def checkGuardsOrComments():
- cont = True
- for p in self.repo.mq.full_series:
- if '#' in p:
- cont = QuestionMsgBox('Confirm qreorder',
- _('<p>ATTENTION!<br>'
- 'Guard or comment found.<br>'
- 'Reordering patches will destroy them.<br>'
- '<br>Continue?</p>'), parent=self,
- defaultbutton=QMessageBox.No)
- break
- return cont
- if checkGuardsOrComments():
- dlg = qreorder.QReorderDialog(self.repo, self)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- def qfoldact():
- dlg = qfold.QFoldDialog(self.repo, self.menuselection, self)
- dlg.finished.connect(dlg.deleteLater)
- dlg.output.connect(self.output)
- dlg.makeLogVisible.connect(self.makeLogVisible)
- dlg.exec_()
- # Special menu for unapplied patches
- if not self.unappcmenu:
- menu = QMenu(self)
- acts = []
- for name, cb in (
- (_('Goto patch'), self.qgotoRevision),
- (_('Rename patch'), self.qrenameRevision),
- (_('Fold patches'), qfoldact),
- (_('Delete patches'), qdeleteact),
- (_('Reorder patches'), qreorderact)):
- act = QAction(name, self)
- act.triggered.connect(cb)
- acts.append(act)
- menu.addAction(act)
- self.unappcmenu = menu
- self.unappacts = acts
- self.menuselection = selection
- self.unappacts[0].setEnabled(len(selection) == 1)
- self.unappacts[1].setEnabled(len(selection) == 1)
- self.unappacts[2].setEnabled('qtip' in self.repo.tags())
- self.unappcmenu.exec_(point)
- def singleSelectionMenu(self, point, selection):
- if not self.singlecmenu:
- items = []
- # isrev = the changeset has an integer revision number
- # isctx = changectx or workingctx, not PatchContext
- # fixed = the changeset is considered permanent
- # patch = any patch applied or not
- # qpar = patch queue parent
- isrev = lambda ap, up, qp, wd: not (up or wd)
- isctx = lambda ap, up, qp, wd: not up
- fixed = lambda ap, up, qp, wd: not (ap or up or wd)
- patch = lambda ap, up, qp, wd: ap or up
- qpar = lambda ap, up, qp, wd: qp
- applied = lambda ap, up, qp, wd: ap
- unapp = lambda ap, up, qp, wd: up
- exs = self.repo.extensions()
- menu = QMenu(self)
- for ext, func, desc, icon, cb in (
- (None, isrev, _('Update...'), 'update',
- self.updateToRevision),
- (None, fixed, _('Merge with...'), 'merge',
- self.mergeWithRevision),
- (None, isctx, _('Browse at rev...'), None,
- self.manifestRevision),
- (None, fixed, _('Tag...'), 'tag', self.tagToRevision),
- ('bookmarks', fixed, _('Bookmark...'), 'bookmark',
- self.bookmarkRevision),
- (None, fixed, _('Backout...'), None, self.backoutToRevision),
- (None, isrev, _('Export patch'), None, self.exportRevisions),
- (None, isrev, _('Email patch...'), None, self.emailRevision),
- (None, isrev, _('Archive...'), None, self.archiveRevision),
- (None, isrev, _('Copy hash'), None, self.copyHash),
- ('transplant', fixed, _('Transplant to local'), None,
- self.transplantRevision),
- ('rebase', None, None, None, None),
- ('rebase', fixed, _('Rebase...'), None, self.rebaseRevision),
- ('mq', None, None, None, None),
- ('mq', fixed, _('Import to MQ'), None, self.qimportRevision),
- ('mq', applied, _('Finish patch'), None, self.qfinishRevision),
- ('mq', qpar, _('Pop all patches'), None, self.qpopAllRevision),
- ('mq', patch, _('Goto patch'), None, self.qgotoRevision),
- ('mq', patch, _('Rename patch'), None, self.qrenameRevision),
- ('mq', fixed, _('Strip...'), None, self.stripRevision),
- ('reviewboard', fixed, _('Post to Review Board...'),
- 'reviewboard', self.sendToReviewBoard)):
- if ext and ext not in exs:
- continue
- if desc is None:
- menu.addSeparator()
- else:
- act = QAction(desc, self)
- act.triggered.connect(cb)
- if icon:
- act.setIcon(geticon(icon))
- act.enableFunc = func
- menu.addAction(act)
- items.append(act)
- self.singlecmenu = menu
- self.singlecmenuitems = items
- ctx = self.repo.changectx(self.rev)
- applied = ctx.thgmqappliedpatch()
- unapp = ctx.thgmqunappliedpatch()
- qparent = 'qparent' in ctx.tags()
- working = self.rev is None
- for item in self.singlecmenuitems:
- enabled = item.enableFunc(applied, unapp, qparent, working)
- item.setEnabled(enabled)
- self.singlecmenu.exec_(point)
- def exportRevisions(self, revisions):
- if not revisions:
- revisions = [self.rev]
- epath = os.path.join(self.repo.root, self.repo.shortname + '_%r.patch')
- cmdline = ['export', '--repository', self.repo.root, '--verbose',
- '--output', epath]
- for rev in revisions:
- cmdline.extend(['--rev', str(rev)])
- self.runCommand(_('Export - TortoiseHg'), cmdline)
- def doubleSelectionMenu(self, point, selection):
- for r in selection:
- # No pair menu if working directory or unapplied patch
- if type(r) is not int:
- return
- def dagrange():
- revA, revB = self.menuselection
- if revA > revB:
- B, A = self.menuselection
- else:
- A, B = self.menuselection
- func = revset.match('%s::%s' % (A, B))
- return [c for c in func(self.repo, range(len(self.repo)))]
- def exportPair():
- self.exportRevisions(self.menuselection)
- def exportDagRange():
- l = dagrange()
- if l:
- self.exportRevisions(l)
- def diffPair():
- revA, revB = self.menuselection
- dlg = visdiff.visualdiff(self.repo.ui, self.repo, [],
- {'rev':(str(revA), str(revB))})
- if dlg:
- dlg.exec_()
- def emailPair():
- run.email(self.repo.ui, rev=self.menuselection, repo=self.repo)
- def emailDagRange():
- l = dagrange()
- if l:
- run.email(self.repo.ui, rev=l, repo=self.repo)
- def bisectNormal():
- revA, revB = self.menuselection
- opts = {'good':str(revA), 'bad':str(revB)}
- dlg = bisect.BisectDialog(self.repo, opts, self)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- def bisectReverse():
- revA, revB = self.menuselection
- opts = {'good':str(revB), 'bad':str(revA)}
- dlg = bisect.BisectDialog(self.repo, opts, self)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- def compressDlg():
- ctxa = self.repo[self.menuselection[0]]
- ctxb = self.repo[self.menuselection[1]]
- if ctxa.ancestor(ctxb) == ctxb:
- revs = self.menuselection[:]
- elif ctxa.ancestor(ctxb) == ctxa:
- revs = self.menuselection[:]
- revs.reverse()
- else:
- InfoMsgBox(_('Unable to compress history'),
- _('Selected changeset pair not related'))
- return
- dlg = compress.CompressDialog(self.repo, revs, self)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- if not self.paircmenu:
- menu = QMenu(self)
- for name, cb in (
- (_('Visual Diff...'), diffPair),
- (_('Export Pair'), exportPair),
- (_('Email Pair...'), emailPair),
- (_('Export DAG Range'), exportDagRange),
- (_('Email DAG Range...'), emailDagRange),
- (_('Bisect - Good, Bad...'), bisectNormal),
- (_('Bisect - Bad, Good...'), bisectReverse),
- (_('Compress History...'), compressDlg)
- ):
- a = QAction(name, self)
- a.triggered.connect(cb)
- menu.addAction(a)
- if 'reviewboard' in self.repo.extensions():
- a = QAction(_('Post Pair to Review Board...'), self)
- a.triggered.connect(self.sendToReviewBoard)
- menu.addAction(a)
- self.paircmenu = menu
- self.menuselection = selection
- self.paircmenu.exec_(point)
- def multipleSelectionMenu(self, point, selection):
- for r in selection:
- # No multi menu if working directory or unapplied patch
- if type(r) is not int:
- return
- def exportSel():
- self.exportRevisions(self.menuselection)
- def emailSel():
- run.email(self.repo.ui, rev=self.menuselection, repo=self.repo)
- if not self.multicmenu:
- menu = QMenu(self)
- for name, cb in (
- (_('Export Selected'), exportSel),
- (_('Email Selected...'), emailSel),
- ):
- a = QAction(name, self)
- a.triggered.connect(cb)
- menu.addAction(a)
- if 'reviewboard' in self.repo.extensions():
- a = QAction(_('Post Selected to Review Board...'), self)
- a.triggered.connect(self.sendToReviewBoard)
- menu.addAction(a)
- self.multicmenu = menu
- self.menuselection = selection
- self.multicmenu.exec_(point)
- def updateToRevision(self):
- dlg = update.UpdateDialog(self.repo, self.rev, self)
- dlg.output.connect(self.output)
- dlg.makeLogVisible.connect(self.makeLogVisible)
- dlg.progress.connect(self.progress)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- def manifestRevision(self):
- run.manifest(self.repo.ui, repo=self.repo, rev=self.rev)
- def mergeWithRevision(self):
- dlg = merge.MergeDialog(self.rev, self.repo, self)
- dlg.exec_()
- def tagToRevision(self):
- dlg = tag.TagDialog(self.repo, rev=str(self.rev), parent=self)
- dlg.localTagChanged.connect(self.refresh)
- dlg.showMessage.connect(self.showMessage)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- def bookmarkRevision(self):
- dlg = bookmark.BookmarkDialog(self.repo, str(self.rev), self)
- dlg.showMessage.connect(self.showMessage)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- def transplantRevision(self):
- cmdline = ['transplant', '--repository', self.repo.root, str(self.rev)]
- self.runCommand(_('Transplant - TortoiseHg'), cmdline)
- def backoutToRevision(self):
- dlg = backout.BackoutDialog(self.repo, str(self.rev), self)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- def stripRevision(self):
- 'Strip the selected revision and all descendants'
- dlg = thgstrip.StripDialog(self.repo, rev=str(self.rev), parent=self)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- def sendToReviewBoard(self):
- run.postreview(self.repo.ui, rev=self.repoview.selectedRevisions(),
- repo=self.repo)
- def emailRevision(self):
- run.email(self.repo.ui, rev=self.repoview.selectedRevisions(),
- repo=self.repo)
- def archiveRevision(self):
- dlg = archive.ArchiveDialog(self.repo.ui, self.repo, self.rev, self)
- dlg.makeLogVisible.connect(self.makeLogVisible)
- dlg.output.connect(self.output)
- dlg.progress.connect(self.progress)
- dlg.exec_()
- def copyHash(self):
- clip = QApplication.clipboard()
- clip.setText(binascii.hexlify(self.repo[self.rev].node()))
- def rebaseRevision(self):
- """Rebase selected revision on top of working directory parent"""
- opts = {'source' : self.rev, 'dest': self.repo['.'].rev()}
- dlg = rebase.RebaseDialog(self.repo, self, **opts)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- def qimportRevision(self):
- """QImport revision and all descendents to MQ"""
- if 'qparent' in self.repo.tags():
- endrev = 'qparent'
- else:
- endrev = ''
- cmdline = ['qimport', '--rev', '%s::%s' % (self.rev, endrev),
- '--repository', self.repo.root]
- self.runCommand(_('QImport - TortoiseHg'), cmdline)
- def qfinishRevision(self):
- """Finish applied patches up to and including selected revision"""
- cmdline = ['qfinish', 'qbase::%s' % self.rev,
- '--repository', self.repo.root]
- self.runCommand(_('QFinish - TortoiseHg'), cmdline)
- def qpopAllRevision(self):
- """Unapply all patches"""
- self.taskTabsWidget.setCurrentIndex(self.mqTabIndex)
- self.mqDemand.get().onPopAll()
- def qgotoRevision(self):
- """Make REV the top applied patch"""
- patchname = self.repo.changectx(self.rev).thgmqpatchname()
- cmdline = ['qgoto', str(patchname), # FIXME force option
- '--repository', self.repo.root]
- self.runCommand(_('QGoto - TortoiseHg'), cmdline)
- def qrenameRevision(self):
- """Rename the selected MQ patch"""
- patchname = self.repo.changectx(self.rev).thgmqpatchname()
- dlg = qrename.QRenameDialog(self.repo, patchname, self)
- dlg.finished.connect(dlg.deleteLater)
- dlg.output.connect(self.output)
- dlg.makeLogVisible.connect(self.makeLogVisible)
- dlg.exec_()
- def runCommand(self, title, cmdline):
- if self.runner:
- InfoMsgBox(_('Unable to start'),
- _('Previous command is still running'))
- return
- def finished(ret):
- self.repo.decrementBusyCount()
- self.runner = None
- self.runner = cmdui.Runner(title, False, self)
- self.runner.output.connect(self.output)
- self.runner.progress.connect(self.progress)
- self.runner.makeLogVisible.connect(self.makeLogVisible)
- self.runner.commandFinished.connect(finished)
- self.repo.incrementBusyCount()
- self.runner.run(cmdline)