by
Changes to 56 files · Browse files at bf1160f429d7 Showing diff from parent 7ab0112dbd39 9ccf60504e07 Diff from another changeset...
|
@@ -23,4 +23,4 @@ Post Major Release:
* Increment minimum Mercurial version in tortoisehg/util/hgversion.py
* Sweep through code and remove hacks for older Mercurial releases
-* Update http://bitbucket.org/tortoisehg/stable/wiki/ReleaseNotes#matching-versions
+* Update http://bitbucket.org/tortoisehg/thg/wiki/ReleaseNotes#matching-versions
|
@@ -6,13 +6,13 @@
To clone a repository you have to run the clone dialog.
From the explorer context menu select :menuselection:`TortoiseHg... --> Clone a repository`
-or type :command:`thg clone`.
+or type :command:`thg clone`.
.. figure:: figures/clone.png
:alt: Clone dialog
Clone Dialog
-
+
:guilabel:`Source`
It is the path (or URL) of the repository that will be cloned. Use
the :guilabel:`Browse...` to choose a local folder.
@@ -39,11 +39,17 @@ To use uncompressed transfer (fast over LAN).
:guilabel:`Include patch queue`
To also clone an MQ patch repository along with the main repository.
+ It is possible to provide a patch queue name that differs from the
+ default one.
:guilabel:`Use proxy server`
To use the proxy server configured in :menuselection:`TortoiseHg... --> Global Settings --> Proxy`.
This is enabled only if a proxy is configured.
+:guilabel:`Do not verify host certificate`
+ Skip checking server certificate for https:// url (ignoring web.cacerts config).
:guilabel:`Remote command`
Specify a Mercurial command to run on the remote side.
+:guilabel:`Hg command`
+ This field displays the command that will be executed by the dialog.
From command line
-----------------
|
@@ -137,8 +137,9 @@
*TortoiseHg Integration*
-Imported Subversion changesets will display the original Subversion
-checkin number in the Repository Explorer browser.
+Imported Subversion changesets will display the original Subversion checkin
+number in the Changeset Info widget in the Revision Details task tab of the
+Workbench.
hg-git (git)
============
|
@@ -45,11 +45,8 @@ demandimport.ignore.append('icons_rc')
demandimport.ignore.append('translations_rc')
demandimport.enable()
-from mercurial import ui as uimod, util
-from tortoisehg.util.hgversion import hgversion, checkhgversion
-import cStringIO
-import traceback
+# Verify we can reach TortoiseHg sources first
try:
import tortoisehg.hgqt.run
except ImportError, e:
@@ -59,50 +56,18 @@ sys.stderr.write("(check your install and PYTHONPATH)\n")
sys.exit(-1)
-ui = uimod.ui()
-capt = ui.configbool('tortoisehg', 'stderrcapt', True)
-
-errors = ('Traceback', 'TypeError', 'NameError', 'AttributeError',
- 'NotImplementedError')
-
-err = checkhgversion(hgversion)
-if err:
+# Verify we have an acceptable version of Mercurial
+from tortoisehg.util.hgversion import hgversion, checkhgversion
+errmsg = checkhgversion(hgversion)
+if errmsg:
from tortoisehg.hgqt.bugreport import run
from tortoisehg.hgqt.run import qtrun
opts = {}
opts['cmd'] = ' '.join(sys.argv[1:])
- opts['error'] = '\n' + err + '\n'
+ opts['error'] = '\n' + errmsg + '\n'
opts['nofork'] = True
qtrun(run, ui, **opts)
sys.exit(1)
-if not capt or 'THGDEBUG' in os.environ or '--profile' in sys.argv:
- sys.exit(tortoisehg.hgqt.run.dispatch(sys.argv[1:]))
-else:
- mystderr = cStringIO.StringIO()
- origstderr = sys.stderr
- sys.stderr = mystderr
- ret = 0
- try:
- ret = tortoisehg.hgqt.run.dispatch(sys.argv[1:])
- sys.stderr = origstderr
- mystderr.seek(0)
- for l in mystderr.readlines():
- if l.startswith(errors):
- from tortoisehg.hgqt.bugreport import run
- from tortoisehg.hgqt.run import qtrun
- error = 'Recoverable runtime error (stderr):\n'
- error += mystderr.getvalue()
- opts = {}
- opts['cmd'] = ' '.join(sys.argv[1:])
- opts['error'] = error
- opts['nofork'] = True
- qtrun(run, ui, **opts)
- break
- sys.exit(ret)
- except:
- if sys.exc_info()[0] not in [SystemExit, KeyboardInterrupt]:
- sys.stderr = origstderr
- traceback.print_exc()
- else:
- raise SystemExit(ret)
+ret = tortoisehg.hgqt.run.dispatch(sys.argv[1:])
+sys.exit(ret)
|
|
|
@@ -1,456 +0,0 @@ - # annotate.py - File annotation widget
-#
-# Copyright 2010 Steve Borho <steve@borho.org>
-#
-# 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 mercurial import ui, error, util
-
-from tortoisehg.hgqt import visdiff, qtlib, qscilib, wctxactions, thgrepo, lexers
-from tortoisehg.util import paths, hglib, colormap, thread2
-from tortoisehg.hgqt.i18n import _
-from tortoisehg.hgqt.grep import SearchWidget
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-from PyQt4.Qsci import QsciScintilla, QsciStyle
-
-# Technical Debt
-# Pass search parameters to grep
-# forward/backward history buttons
-# menu options for viewing appropriate changesets
-
-class AnnotateView(qscilib.Scintilla):
- revisionHint = pyqtSignal(QString)
-
- searchRequested = pyqtSignal(QString)
- """Emitted (pattern) when user request to search content"""
-
- editSelected = pyqtSignal(unicode, object, int)
- """Emitted (path, rev, line) when user requests to open editor"""
-
- grepRequested = pyqtSignal(QString, dict)
- """Emitted (pattern, **opts) when user request to search changelog"""
-
- sourceChanged = pyqtSignal(unicode, object)
- """Emitted (path, rev) when the content source changed"""
-
- def __init__(self, repo, parent=None, **opts):
- super(AnnotateView, self).__init__(parent)
- self.setReadOnly(True)
- self.setMarginLineNumbers(1, True)
- self.setMarginType(2, QsciScintilla.TextMarginRightJustified)
- self.setMouseTracking(True)
- self.setFont(qtlib.getfont('fontdiff').font())
- self.setContextMenuPolicy(Qt.CustomContextMenu)
- self.customContextMenuRequested.connect(self.menuRequest)
-
- self.repo = repo
- self.repo.configChanged.connect(self.configChanged)
- self.configChanged()
- self._rev = None
- self.annfile = None
- self._annotation_enabled = bool(opts.get('annotationEnabled', False))
-
- self._links = [] # by line
- self._revmarkers = {} # by rev
- self._lastrev = None
-
- self._thread = _AnnotateThread(self)
- self._thread.finished.connect(self.fillModel)
-
- def configChanged(self):
- self.setIndentationWidth(self.repo.tabwidth)
- self.setTabWidth(self.repo.tabwidth)
-
- def keyPressEvent(self, event):
- if event.key() == Qt.Key_Escape:
- self._thread.abort()
- return
- return super(AnnotateView, self).keyPressEvent(event)
-
- def mouseMoveEvent(self, event):
- self._emitRevisionHintAtLine(self.lineAt(event.pos()))
- super(AnnotateView, self).mouseMoveEvent(event)
-
- def _emitRevisionHintAtLine(self, line):
- if line < 0:
- return
- try:
- fctx = self._links[line][0]
- if fctx.rev() != self._lastrev:
- s = hglib.get_revision_desc(fctx, self.annfile)
- self.revisionHint.emit(s)
- self._lastrev = fctx.rev()
- except IndexError:
- pass
-
- @pyqtSlot(QPoint)
- def menuRequest(self, point):
- menu = self.createStandardContextMenu()
- line = self.lineAt(point)
- point = self.mapToGlobal(point)
-
- if not self.isAnnotationEnabled():
- return menu.exec_(point)
- if line < 0 or line >= len(self._links):
- return menu.exec_(point)
-
- fctx, line = self._links[line]
- data = [hglib.tounicode(fctx.path()), fctx.rev(), line]
-
- if self.hasSelectedText():
- selection = self.selectedText()
- def sreq(**opts):
- return lambda: self.grepRequested.emit(selection, opts)
- def sann():
- self.searchRequested.emit(selection)
- menu.addSeparator()
- for name, func in [(_('Search in original revision'),
- sreq(rev=fctx.rev())),
- (_('Search in working revision'),
- sreq(rev='.')),
- (_('Search in current annotation'), sann),
- (_('Search in history'), sreq(all=True))]:
- def add(name, func):
- action = menu.addAction(name)
- action.triggered.connect(func)
- add(name, func)
-
- def annorig():
- self.setSource(*data)
- def editorig():
- self.editSelected.emit(*data)
- menu.addSeparator()
- for name, func in [(_('Annotate originating revision'), annorig),
- (_('View originating revision'), editorig)]:
- def add(name, func):
- action = menu.addAction(name)
- action.triggered.connect(func)
- add(name, func)
- for pfctx in fctx.parents():
- pdata = [hglib.tounicode(pfctx.path()), pfctx.changectx().rev(),
- line]
- def annparent(data):
- self.setSource(*data)
- def editparent(data):
- self.editSelected.emit(*data)
- for name, func in [(_('Annotate parent revision %d') % pdata[1],
- annparent),
- (_('View parent revision %d') % pdata[1],
- editparent)]:
- def add(name, func):
- action = menu.addAction(name)
- action.data = pdata
- action.run = lambda: func(action.data)
- action.triggered.connect(action.run)
- add(name, func)
- menu.exec_(point)
-
- @property
- def rev(self):
- """Returns the current revision number"""
- return self._rev
-
- @pyqtSlot(unicode, object, int)
- def setSource(self, wfile, rev, line=None):
- """Change the content to the specified file at rev [unicode]
-
- line is counted from 1.
- """
- if isinstance(wfile, (unicode, QString)):
- # setSource can be used as a slot, in which case wfile is a QString
- lwfile = hglib.fromunicode(wfile)
- else:
- # or it can be called directly with a local encoded wfile string
- lwfile = wfile
- wfile = hglib.tounicode(wfile)
-
- if self.annfile == lwfile and self.rev == rev:
- if line:
- self.setCursorPosition(int(line) - 1, 0)
- return
-
- try:
- ctx = self.repo[rev]
- fctx = ctx[lwfile]
- except error.LookupError:
- qtlib.ErrorMsgBox(_('Unable to annotate'),
- _('%s is not found in revision %d') % (wfile, ctx.rev()))
- return
-
- try:
- if rev is None:
- size = fctx.size()
- else:
- size = fctx._filelog.rawsize(fctx.filerev())
- except (EnvironmentError, error.LookupError), e:
- self.setText(_('File or diffs not displayed: ') + \
- hglib.tounicode(str(e)))
- self.error = p + hglib.tounicode(str(e))
- return
-
- if size > ctx._repo.maxdiff:
- self.setText(_('File or diffs not displayed: ') + \
- _('File is larger than the specified max size.\n'))
- else:
- self._rev = ctx.rev()
- self.clear()
- self.annfile = lwfile
- if util.binary(fctx.data()):
- self.setText(_('File is binary.\n'))
- else:
- self.setText(hglib.tounicode(fctx.data()))
- if line:
- self.setCursorPosition(int(line) - 1, 0)
- self._updatelexer(fctx)
- self._updatemarginwidth()
- self.sourceChanged.emit(wfile, self._rev)
- self._updateannotation()
-
- def _updateannotation(self):
- if not self.isAnnotationEnabled() or not self.annfile:
- return
- ctx = self.repo[self._rev]
- fctx = ctx[self.annfile]
- if util.binary(fctx.data()):
- return
- self._thread.abort()
- self._thread.start(fctx)
-
- @pyqtSlot()
- def fillModel(self):
- self._thread.wait()
- if self._thread.data is None:
- return
-
- self._links = list(self._thread.data)
-
- self._updaterevmargin()
- self._updatemarkers()
- self._updatemarginwidth()
-
- def clear(self):
- super(AnnotateView, self).clear()
- self.clearMarginText()
- self.markerDeleteAll()
- self.annfile = None
-
- @pyqtSlot(bool)
- def setAnnotationEnabled(self, enabled):
- """Enable / disable annotation"""
- enabled = bool(enabled)
- if enabled == self.isAnnotationEnabled():
- return
- self._annotation_enabled = enabled
- self._updateannotation()
- self._updatemarginwidth()
- self.setMouseTracking(enabled)
- if not self.isAnnotationEnabled():
- self.annfile = None
- self.markerDeleteAll()
-
- def isAnnotationEnabled(self):
- """True if annotation enabled and available"""
- if self.rev is None:
- return False # annotate working copy is not supported
- return self._annotation_enabled
-
- def _updatelexer(self, fctx):
- """Update the lexer according to the given file"""
- lex = lexers.get_lexer(fctx.path(), hglib.tounicode(fctx.data()), self)
- self.setLexer(lex)
- if lex is None:
- self.setFont(qtlib.getfont('fontlog').font())
-
- def _updaterevmargin(self):
- """Update the content of margin area showing revisions"""
- s = self._margin_style
- # Workaround to set style of the current sci widget.
- # QsciStyle sends style data only to the first sci widget.
- # See qscintilla2/Qt4/qscistyle.cpp
- self.SendScintilla(QsciScintilla.SCI_STYLESETBACK,
- s.style(), s.paper())
- self.SendScintilla(QsciScintilla.SCI_STYLESETFONT,
- s.style(), s.font().family().toAscii().data())
- self.SendScintilla(QsciScintilla.SCI_STYLESETSIZE,
- s.style(), s.font().pointSize())
- for i, (fctx, _origline) in enumerate(self._links):
- self.setMarginText(i, str(fctx.rev()), s)
-
- def _updatemarkers(self):
- """Update markers which colorizes each line"""
- self._redefinemarkers()
- for i, (fctx, _origline) in enumerate(self._links):
- m = self._revmarkers.get(fctx.rev())
- if m is not None:
- self.markerAdd(i, m)
-
- def _redefinemarkers(self):
- """Redefine line markers according to the current revs"""
- curdate = self.repo[self._rev].date()[0]
-
- # make sure to colorize at least 1 year
- mindate = curdate - 365 * 24 * 60 * 60
-
- self._revmarkers.clear()
- filectxs = iter(fctx for fctx, _origline in self._links)
- palette = colormap.makeannotatepalette(filectxs, curdate,
- maxcolors=32, maxhues=8,
- maxsaturations=16,
- mindate=mindate)
- for i, (color, fctxs) in enumerate(palette.iteritems()):
- self.markerDefine(QsciScintilla.Background, i)
- self.setMarkerBackgroundColor(QColor(color), i)
- for fctx in fctxs:
- self._revmarkers[fctx.rev()] = i
-
- @util.propertycache
- def _margin_style(self):
- """Style for margin area"""
- s = QsciStyle()
- s.setPaper(QApplication.palette().color(QPalette.Window))
- s.setFont(self.font())
- return s
-
- @pyqtSlot()
- def _updatemarginwidth(self):
- self.setMarginsFont(self.font())
- def lentext(s):
- return 'M' * (len(str(s)) + 2) # 2 for margin
- self.setMarginWidth(1, lentext(self.lines()))
- if self.isAnnotationEnabled() and self._links:
- maxrev = max(fctx.rev() for fctx, _origline in self._links)
- self.setMarginWidth(2, lentext(maxrev))
- else:
- self.setMarginWidth(2, 0)
-
-class _AnnotateThread(QThread):
- 'Background thread for annotating a file at a revision'
- def __init__(self, parent=None):
- super(_AnnotateThread, self).__init__(parent)
- self._threadid = None
-
- @pyqtSlot(object)
- def start(self, fctx):
- self._fctx = fctx
- super(_AnnotateThread, self).start()
- self.data = None
-
- @pyqtSlot()
- def abort(self):
- if self._threadid is None:
- return
- try:
- thread2._async_raise(self._threadid, KeyboardInterrupt)
- self.wait()
- except ValueError:
- pass
-
- def run(self):
- assert self.currentThread() != qApp.thread()
- self._threadid = self.currentThreadId()
- try:
- try:
- data = []
- for (fctx, line), _text in self._fctx.annotate(True, True):
- data.append((fctx, line))
- self.data = data
- except KeyboardInterrupt:
- pass
- finally:
- self._threadid = None
- del self._fctx
-
-class AnnotateDialog(QMainWindow):
- def __init__(self, *pats, **opts):
- super(AnnotateDialog,self).__init__(opts.get('parent'), Qt.Window)
-
- root = opts.get('root') or paths.find_root()
- repo = thgrepo.repository(ui.ui(), path=root)
- # TODO: handle repo not found
-
- av = AnnotateView(repo, self, annotationEnabled=True)
- self.setCentralWidget(av)
- self.av = av
-
- status = QStatusBar()
- self.setStatusBar(status)
- av.revisionHint.connect(status.showMessage)
- av.editSelected.connect(self.editSelected)
- av.grepRequested.connect(self._openSearchWidget)
-
- self._searchbar = qscilib.SearchToolBar()
- self.addToolBar(self._searchbar)
- self._searchbar.setPattern(hglib.tounicode(opts.get('pattern', '')))
- self._searchbar.searchRequested.connect(self.av.find)
- self._searchbar.conditionChanged.connect(self.av.highlightText)
- av.searchRequested.connect(self._searchbar.search)
- QShortcut(QKeySequence.Find, self,
- lambda: self._searchbar.setFocus(Qt.OtherFocusReason))
-
- self.av.sourceChanged.connect(
- lambda *args: self.setWindowTitle(_('Annotate %s@%d') % args))
-
- self.searchwidget = opts.get('searchwidget')
-
- self.opts = opts
- line = opts.get('line')
- if line and isinstance(line, str):
- line = int(line)
-
- self.repo = repo
-
- self.restoreSettings()
-
- # run heavy operation after the dialog visible
- path = hglib.tounicode(pats[0])
- rev = opts.get('rev') or '.'
- QTimer.singleShot(0, lambda: av.setSource(path, rev, line))
-
- def closeEvent(self, event):
- self.storeSettings()
- super(AnnotateDialog, self).closeEvent(event)
-
- def editSelected(self, wfile, rev, line):
- pattern = hglib.fromunicode(self._searchbar._le.text()) or None
- wfile = hglib.fromunicode(wfile)
- repo = self.repo
- try:
- ctx = repo[rev]
- fctx = ctx[wfile]
- except Exception, e:
- self.statusBar().showMessage(hglib.tounicode(str(e)))
-
- base, _ = visdiff.snapshot(repo, [wfile], repo[rev])
- files = [os.path.join(base, wfile)]
- wctxactions.edit(self, repo.ui, repo, files, line, pattern)
-
- @pyqtSlot(unicode, dict)
- def _openSearchWidget(self, pattern, opts):
- opts = dict((str(k), str(v)) for k, v in opts.iteritems())
- if self.searchwidget is None:
- self.searchwidget = SearchWidget([pattern], repo=self.repo,
- **opts)
- self.searchwidget.show()
- else:
- self.searchwidget.setSearch(pattern, **opts)
- self.searchwidget.show()
- self.searchwidget.raise_()
-
- def storeSettings(self):
- s = QSettings()
- s.setValue('annotate/geom', self.saveGeometry())
- self.av.saveSettings(s, 'annotate/av')
-
- def restoreSettings(self):
- s = QSettings()
- self.restoreGeometry(s.value('annotate/geom').toByteArray())
- self.av.loadSettings(s, 'annotate/av')
-
-def run(ui, *pats, **opts):
- pats = hglib.canonpaths(pats)
- return AnnotateDialog(*pats, **opts)
|
|
|
@@ -5,275 +5,560 @@ # This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from mercurial import merge as mergemod
+from mercurial import hg, merge as mergemod
from tortoisehg.util import hglib
from tortoisehg.hgqt.i18n import _
from tortoisehg.hgqt import qtlib, csinfo, i18n, cmdui, status, resolve
from tortoisehg.hgqt import commit, qscilib, thgrepo
-keep = i18n.keepgettext()
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
-class BackoutDialog(QDialog):
+class BackoutDialog(QWizard):
- def __init__(self, repo, rev='tip', parent=None, opts={}):
+ def __init__(self, rev, repo, parent):
super(BackoutDialog, self).__init__(parent)
f = self.windowFlags()
self.setWindowFlags(f & ~Qt.WindowContextHelpButtonHint)
+
+ self.backoutrev = rev
+ self.parentbackout = False
+
+ self.setWindowTitle(_('Backout - %s') % repo.displayname)
self.setWindowIcon(qtlib.geticon('hg-revert'))
+ self.setOption(QWizard.NoBackButtonOnStartPage, True)
+ self.setOption(QWizard.NoBackButtonOnLastPage, True)
+ self.setOption(QWizard.IndependentPages, True)
+
+ self.addPage(SummaryPage(repo, self))
+ self.addPage(BackoutPage(repo, self))
+ self.addPage(CommitPage(repo, self))
+ self.addPage(ResultPage(repo, self))
+ self.currentIdChanged.connect(self.pageChanged)
+
+ self.resize(QSize(700, 489).expandedTo(self.minimumSizeHint()))
+
+ repo.repositoryChanged.connect(self.repositoryChanged)
+ repo.configChanged.connect(self.configChanged)
+
+ def repositoryChanged(self):
+ self.currentPage().repositoryChanged()
+
+ def configChanged(self):
+ self.currentPage().configChanged()
+
+ def pageChanged(self, id):
+ if id != -1:
+ self.currentPage().currentPage()
+
+ def reject(self):
+ if self.currentPage().canExit():
+ super(BackoutDialog, self).reject()
+
+
+class BasePage(QWizardPage):
+ def __init__(self, repo, parent):
+ super(BasePage, self).__init__(parent)
self.repo = repo
- # main layout box
- box = QVBoxLayout()
- box.setSpacing(8)
- box.setContentsMargins(*(6,)*4)
+ def validatePage(self):
+ 'user pressed NEXT button, can we proceed?'
+ return True
- ## target revision
- target_sep = qtlib.LabeledSeparator(_('Target changeset'))
- box.addWidget(target_sep)
+ def isComplete(self):
+ 'should NEXT button be sensitive?'
+ return True
- style = csinfo.panelstyle(selectable=True)
- self.targetinfo = csinfo.create(self.repo, rev, style, withupdate=True)
- box.addWidget(self.targetinfo)
+ def repositoryChanged(self):
+ 'repository has detected a change to changelog or parents'
+ pass
- ## backout message
- msg_sep = qtlib.LabeledSeparator(_('Backout commit message'))
- box.addWidget(msg_sep)
+ def configChanged(self):
+ 'repository has detected a change to config files'
+ pass
- revhex = self.targetinfo.get_data('revid')
- self.msgset = keep._('Backed out changeset: ')
- self.msgset['id'] += revhex
- self.msgset['str'] += revhex
+ def currentPage(self):
+ pass
- self.msgTextEdit = commit.MessageEntry(self)
- self.msgTextEdit.installEventFilter(qscilib.KeyPressInterceptor(self))
- self.msgTextEdit.refresh(repo)
- self.msgTextEdit.loadSettings(QSettings(), 'backout/message')
- self.msgTextEdit.setText(self.msgset['str'])
- box.addWidget(self.msgTextEdit, 2)
+ def canExit(self):
+ return True
- ## options
- opt_sep = qtlib.LabeledSeparator(_('Options'))
- box.addWidget(opt_sep)
- obox = QVBoxLayout()
- obox.setSpacing(3)
- box.addLayout(obox)
+class SummaryPage(BasePage):
- self.engChk = QCheckBox(_('Use English backout message'))
- self.engChk.toggled.connect(self.eng_toggled)
- engmsg = self.repo.ui.configbool('tortoisehg', 'engmsg', False)
- self.engChk.setChecked(engmsg)
+ def __init__(self, repo, parent):
+ super(SummaryPage, self).__init__(repo, parent)
+ self.clean = False
+ self.th = None
- obox.addWidget(self.engChk)
- self.mergeChk = QCheckBox(_('Commit backout before merging with '
- 'current working parent'))
- self.mergeChk.toggled.connect(self.merge_toggled)
- self.mergeChk.setChecked(bool(opts.get('merge')))
- self.msgTextEdit.setEnabled(False)
- obox.addWidget(self.mergeChk)
+ def initializePage(self):
+ if self.layout():
+ return
+ self.setTitle(_('Prepare to backout'))
+ self.setSubTitle(_('Verify backout revision and ensure your working '
+ 'directory is clean.'))
+ self.setLayout(QVBoxLayout())
- self.autoresolve_chk = QCheckBox(_('Automatically resolve merge conflicts '
- 'where possible'))
- self.autoresolve_chk.setChecked(
+ repo = self.repo
+ try:
+ bctx = repo[self.wizard().backoutrev]
+ pctx = repo['.']
+ except error.RepoLookupError:
+ qtlib.InfoMsgBox(_('Unable to backout'),
+ _('Backout revision not found'))
+ QTimer.singleShot(0, self.wizard().close)
+
+ if pctx == bctx:
+ lbl = _('Backing out a parent revision is a single step operation')
+ self.layout().addWidget(QLabel(u'<b>%s</b>' % lbl))
+ self.wizard().parentbackout = True
+
+ op1, op2 = repo.dirstate.parents()
+ a = repo.changelog.ancestor(op1, bctx.node())
+ if a != bctx.node():
+ qtlib.InfoMsgBox(_('Unable to backout'),
+ _('Cannot backout change on a different branch'))
+ QTimer.singleShot(0, self.wizard().close)
+
+ ## backout revision
+ style = csinfo.panelstyle(contents=csinfo.PANEL_DEFAULT)
+ create = csinfo.factory(repo, None, style, withupdate=True)
+ sep = qtlib.LabeledSeparator(_('Backout revision'))
+ self.layout().addWidget(sep)
+ backoutCsInfo = create(bctx.rev())
+ self.layout().addWidget(backoutCsInfo)
+
+ ## current revision
+ contents = ('ishead',) + csinfo.PANEL_DEFAULT
+ style = csinfo.panelstyle(contents=contents)
+ def markup_func(widget, item, value):
+ if item == 'ishead' and value is False:
+ text = _('Not a head, backout will create a new head!')
+ return qtlib.markup(text, fg='red', weight='bold')
+ raise csinfo.UnknownItem(item)
+ custom = csinfo.custom(markup=markup_func)
+ create = csinfo.factory(repo, custom, style, withupdate=True)
+
+ sep = qtlib.LabeledSeparator(_('Current local revision'))
+ self.layout().addWidget(sep)
+ localCsInfo = create(pctx.rev())
+ self.layout().addWidget(localCsInfo)
+ self.localCsInfo = localCsInfo
+
+ ## working directory status
+ sep = qtlib.LabeledSeparator(_('Working directory status'))
+ self.layout().addWidget(sep)
+
+ self.groups = qtlib.WidgetGroups()
+
+ wdbox = QHBoxLayout()
+ self.layout().addLayout(wdbox)
+ self.wd_status = qtlib.StatusLabel()
+ self.wd_status.set_status(_('Checking...'))
+ wdbox.addWidget(self.wd_status)
+ wd_prog = QProgressBar()
+ wd_prog.setMaximum(0)
+ wd_prog.setTextVisible(False)
+ self.groups.add(wd_prog, 'prog')
+ wdbox.addWidget(wd_prog, 1)
+
+ text = _('Before backout, you must <a href="commit"><b>commit</b></a>, '
+ '<a href="shelve"><b>shelve</b></a> to patch, '
+ 'or <a href="discard"><b>discard</b></a> changes.')
+ wd_text = QLabel(text)
+ wd_text.setWordWrap(True)
+ wd_text.linkActivated.connect(self.onLinkActivated)
+ self.wd_text = wd_text
+ self.groups.add(wd_text, 'dirty')
+ self.layout().addWidget(wd_text)
+
+ ## auto-resolve
+ autoresolve_chk = QCheckBox(_('Automatically resolve merge conflicts '
+ 'where possible'))
+ autoresolve_chk.setChecked(
repo.ui.configbool('tortoisehg', 'autoresolve', False))
- obox.addWidget(self.autoresolve_chk)
+ self.registerField('autoresolve', autoresolve_chk)
+ self.layout().addWidget(autoresolve_chk)
+ self.autoresolve_chk = autoresolve_chk
+ self.groups.set_visible(False, 'dirty')
- if repo[revhex] == repo.parents()[0]:
- # backing out the working parent is a one-step process
- self.msgTextEdit.setEnabled(True)
- self.mergeChk.setVisible(False)
- self.autoresolve_chk.setVisible(False)
- self.backoutParent = True
+ def isComplete(self):
+ 'should Next button be sensitive?'
+ return self.clean
+
+ def repositoryChanged(self):
+ 'repository has detected a change to changelog or parents'
+ pctx = self.repo['.']
+ self.localCsInfo.update(pctx)
+ self.wizard().localrev = str(pctx.rev())
+
+ def canExit(self):
+ 'can backout tool be closed?'
+ if self.th is not None and self.th.isRunning():
+ self.th.cancel()
+ self.th.wait()
+ return True
+
+ def currentPage(self):
+ self.refresh()
+
+ def refresh(self):
+ if self.th is None:
+ self.th = CheckThread(self.repo, self)
+ self.th.finished.connect(self.threadFinished)
+ if self.th.isRunning():
+ return
+ self.groups.set_visible(True, 'prog')
+ self.th.start()
+
+ def threadFinished(self):
+ self.groups.set_visible(False, 'prog')
+ if self.th.canceled:
+ return
+ dirty, parents = self.th.results
+ self.clean = not dirty
+ if dirty:
+ self.groups.set_visible(True, 'dirty')
+ self.wd_status.set_status(_('<b>Uncommitted local changes '
+ 'are detected</b>'), 'thg-warning')
else:
- self.backoutParent = False
+ self.groups.set_visible(False, 'dirty')
+ self.wd_status.set_status(_('Clean'), True)
+ self.completeChanged.emit()
+
+ @pyqtSlot(QString)
+ def onLinkActivated(self, cmd):
+ cmd = hglib.fromunicode(cmd)
+ repo = self.repo
+ if cmd == 'commit':
+ dlg = commit.CommitDialog(repo, [], {}, self.wizard())
+ dlg.finished.connect(dlg.deleteLater)
+ dlg.exec_()
+ self.refresh()
+ elif cmd == 'shelve':
+ from tortoisehg.hgqt import shelve
+ dlg = shelve.ShelveDialog(repo, self.wizard())
+ dlg.finished.connect(dlg.deleteLater)
+ dlg.exec_()
+ self.refresh()
+ elif cmd.startswith('discard'):
+ if cmd != 'discard:noconfirm':
+ labels = [(QMessageBox.Yes, _('&Discard')),
+ (QMessageBox.No, _('Cancel'))]
+ if not qtlib.QuestionMsgBox(_('Confirm Discard'),
+ _('Discard outstanding changes to working directory?'),
+ labels=labels, parent=self):
+ return
+ def finished(ret):
+ repo.decrementBusyCount()
+ self.refresh()
+ cmdline = ['update', '--clean', '--repository', repo.root,
+ '--rev', '.']
+ self.runner = cmdui.Runner(True, self)
+ self.runner.commandFinished.connect(finished)
+ repo.incrementBusyCount()
+ self.runner.run(cmdline)
+ elif cmd == 'view':
+ dlg = status.StatusDialog(repo, [], {}, self)
+ dlg.exec_()
+ self.refresh()
+ else:
+ raise 'unknown command: %s' % cmd
+
+
+class BackoutPage(BasePage):
+ def __init__(self, repo, parent):
+ super(BackoutPage, self).__init__(repo, parent)
+ self.backoutcomplete = False
+
+ self.setTitle(_('Backing out, then merging...'))
+ self.setSubTitle(_('All conflicting files will be marked unresolved.'))
+ self.setLayout(QVBoxLayout())
+
+ self.cmd = cmdui.Widget(True, False, self)
+ self.cmd.commandFinished.connect(self.onCommandFinished)
+ self.cmd.setShowOutput(True)
+ self.layout().addWidget(self.cmd)
self.reslabel = QLabel()
- self.reslabel.linkActivated.connect(self.link_activated)
- box.addWidget(self.reslabel)
+ self.reslabel.linkActivated.connect(self.onLinkActivated)
+ self.reslabel.setWordWrap(True)
+ self.layout().addWidget(self.reslabel)
- ## command widget
+ self.autonext = QCheckBox(_('Automatically advance to next page '
+ 'when backout and merge are complete.'))
+ checked = QSettings().value('backout/autoadvance', False).toBool()
+ self.autonext.setChecked(checked)
+ self.autonext.toggled.connect(self.tryAutoAdvance)
+ self.layout().addWidget(self.autonext)
+
+ def currentPage(self):
+ if self.wizard().parentbackout:
+ self.wizard().next()
+ return
+ cmdline = ['--repository', self.repo.root, 'backout']
+ tool = self.field('autoresolve').toBool() and 'merge' or 'fail'
+ cmdline += ['--tool=internal:' + tool]
+ cmdline += ['--rev', str(self.wizard().backoutrev)]
+ self.repo.incrementBusyCount()
+ self.cmd.core.clearOutput()
+ self.cmd.run(cmdline)
+
+ def isComplete(self):
+ 'should Next button be sensitive?'
+ if not self.backoutcomplete:
+ return False
+ count = 0
+ for root, path, status in thgrepo.recursiveMergeStatus(self.repo):
+ if status == 'u':
+ count += 1
+ if count:
+ # if autoresolve is enabled, we know these were real conflicts
+ self.reslabel.setText(_('%d files have <b>merge conflicts</b> '
+ 'that must be <a href="resolve">'
+ '<b>resolved</b></a>') % count)
+ return False
+ else:
+ self.reslabel.setText(_('No merge conflicts, ready to commit'))
+ return True
+
+ def tryAutoAdvance(self, checked):
+ if checked and self.isComplete():
+ self.wizard().next()
+
+ def cleanupPage(self):
+ QSettings().setValue('backout/autoadvance', self.autonext.isChecked())
+
+ def onCommandFinished(self, ret):
+ self.repo.decrementBusyCount()
+ if ret in (0, 1):
+ self.backoutcomplete = True
+ if self.autonext.isChecked():
+ self.tryAutoAdvance(True)
+ self.completeChanged.emit()
+
+ @pyqtSlot(QString)
+ def onLinkActivated(self, cmd):
+ if cmd == 'resolve':
+ dlg = resolve.ResolveDialog(self.repo, self)
+ dlg.finished.connect(dlg.deleteLater)
+ dlg.exec_()
+ if self.autonext.isChecked():
+ self.tryAutoAdvance(True)
+ self.completeChanged.emit()
+
+
+class CommitPage(BasePage):
+
+ def __init__(self, repo, parent):
+ super(CommitPage, self).__init__(repo, parent)
+ self.commitComplete = False
+
+ self.setTitle(_('Commit backout and merge results'))
+ self.setLayout(QVBoxLayout())
+ self.setCommitPage(True)
+
+ # csinfo
+ def label_func(widget, item, ctx):
+ if item == 'rev':
+ return _('Revision:')
+ elif item == 'parents':
+ return _('Parents')
+ raise csinfo.UnknownItem()
+ def data_func(widget, item, ctx):
+ if item == 'rev':
+ return _('Working Directory'), str(ctx)
+ elif item == 'parents':
+ parents = []
+ cbranch = ctx.branch()
+ for pctx in ctx.parents():
+ branch = None
+ if hasattr(pctx, 'branch') and pctx.branch() != cbranch:
+ branch = pctx.branch()
+ parents.append((str(pctx.rev()), str(pctx), branch, pctx))
+ return parents
+ raise csinfo.UnknownItem()
+ def markup_func(widget, item, value):
+ if item == 'rev':
+ text, rev = value
+ if self.wizard() and self.wizard().parentbackout:
+ return '%s (%s)' % (text, rev)
+ else:
+ return '<a href="view">%s</a> (%s)' % (text, rev)
+ elif item == 'parents':
+ def branch_markup(branch):
+ opts = dict(fg='black', bg='#aaffaa')
+ return qtlib.markup(' %s ' % branch, **opts)
+ csets = []
+ for rnum, rid, branch, pctx in value:
+ line = '%s (%s)' % (rnum, rid)
+ if branch:
+ line = '%s %s' % (line, branch_markup(branch))
+ msg = widget.info.get_data('summary', widget,
+ pctx, widget.custom)
+ if msg:
+ line = '%s %s' % (line, msg)
+ csets.append(line)
+ return csets
+ raise csinfo.UnknownItem()
+ custom = csinfo.custom(label=label_func, data=data_func,
+ markup=markup_func)
+ contents = ('rev', 'user', 'dateage', 'branch', 'parents')
+ style = csinfo.panelstyle(contents=contents, margin=6)
+
+ # merged files
+ rev_sep = qtlib.LabeledSeparator(_('Working Directory (merged)'))
+ self.layout().addWidget(rev_sep)
+ bkCsInfo = csinfo.create(repo, None, style, custom=custom,
+ withupdate=True)
+ bkCsInfo.linkActivated.connect(self.onLinkActivated)
+ self.layout().addWidget(bkCsInfo)
+
+ # commit message area
+ msg_sep = qtlib.LabeledSeparator(_('Commit message'))
+ self.layout().addWidget(msg_sep)
+ msgEntry = commit.MessageEntry(self)
+ msgEntry.installEventFilter(qscilib.KeyPressInterceptor(self))
+ msgEntry.refresh(repo)
+ msgEntry.loadSettings(QSettings(), 'backout/message')
+
+ msgEntry.textChanged.connect(self.completeChanged)
+ self.layout().addWidget(msgEntry)
+ self.msgEntry = msgEntry
+
self.cmd = cmdui.Widget(True, False, self)
- self.cmd.commandStarted.connect(self.command_started)
- self.cmd.commandFinished.connect(self.command_finished)
- self.cmd.commandCanceling.connect(self.command_canceling)
- box.addWidget(self.cmd, 1)
+ self.cmd.commandFinished.connect(self.onCommandFinished)
+ self.cmd.setShowOutput(False)
+ self.layout().addWidget(self.cmd)
- ## bottom buttons
- buttons = QDialogButtonBox()
- self.cancelBtn = buttons.addButton(QDialogButtonBox.Cancel)
- self.cancelBtn.clicked.connect(self.cancel_clicked)
- self.closeBtn = buttons.addButton(QDialogButtonBox.Close)
- self.closeBtn.clicked.connect(self.reject)
- self.backoutBtn = buttons.addButton(_('&Backout'),
- QDialogButtonBox.ActionRole)
- self.backoutBtn.clicked.connect(self.backout)
- self.detailBtn = buttons.addButton(_('Detail'),
- QDialogButtonBox.ResetRole)
- self.detailBtn.setAutoDefault(False)
- self.detailBtn.setCheckable(True)
- self.detailBtn.toggled.connect(self.detail_toggled)
- box.addWidget(buttons)
+ def tryperform():
+ if self.isComplete():
+ self.wizard().next()
+ actionEnter = QAction('alt-enter', self)
+ actionEnter.setShortcuts([Qt.CTRL+Qt.Key_Return, Qt.CTRL+Qt.Key_Enter])
+ actionEnter.triggered.connect(tryperform)
+ self.addAction(actionEnter)
- # dialog setting
- self.setLayout(box)
- self.setMinimumWidth(480)
- self.setMaximumHeight(800)
- self.resize(0, 340)
- self.setWindowTitle(_("Backout '%s' - %s") % (revhex,
- self.repo.displayname))
+ self.skiplast = QCheckBox(_('Skip final confirmation page, '
+ 'close after commit.'))
+ checked = QSettings().value('backout/skiplast', False).toBool()
+ self.skiplast.setChecked(checked)
+ self.layout().addWidget(self.skiplast)
- # prepare to show
- self.cmd.setHidden(True)
- self.cancelBtn.setHidden(True)
- self.detailBtn.setHidden(True)
- self.msgTextEdit.setFocus()
- self.msgTextEdit.moveCursorToEnd()
+ def refresh(self):
+ pass
- ### Private Methods ###
+ def cleanupPage(self):
+ s = QSettings()
+ s.setValue('backout/skiplast', self.skiplast.isChecked())
+ self.msgEntry.saveSettings(s, 'backout/message')
- def merge_toggled(self, checked):
- self.msgTextEdit.setEnabled(checked)
+ def currentPage(self):
+ engmsg = self.repo.ui.configbool('tortoisehg', 'engmsg', False)
+ msgset = i18n.keepgettext()._('Backed out changeset: ')
+ msg = engmsg and msgset['id'] or msgset['str']
+ self.msgEntry.setText(msg + str(self.repo[self.wizard().backoutrev]))
+ self.msgEntry.moveCursorToEnd()
- def eng_toggled(self, checked):
- msg = self.msgTextEdit.text()
- origmsg = (checked and self.msgset['str'] or self.msgset['id'])
- if msg != origmsg:
- if not qtlib.QuestionMsgBox(_('Confirm Discard Message'),
- _('Discard current backout message?'), parent=self):
- self.engChk.blockSignals(True)
- self.engChk.setChecked(not checked)
- self.engChk.blockSignals(False)
- return
- newmsg = (checked and self.msgset['id'] or self.msgset['str'])
- self.msgTextEdit.setText(newmsg)
+ @pyqtSlot(QString)
+ def onLinkActivated(self, cmd):
+ if cmd == 'view':
+ dlg = status.StatusDialog(self.repo, [], {}, self)
+ dlg.exec_()
+ self.refresh()
- def backout(self):
- # prepare command line
- revhex = self.targetinfo.get_data('revid')
- cmdline = ['backout', '--rev', revhex, '--repository', self.repo.root]
- cmdline += ['--tool=internal:' +
- (self.autoresolve_chk.isChecked() and 'merge' or 'fail')]
- if self.backoutParent:
- msg = self.msgTextEdit.text()
- cmdline += ['--message='+hglib.fromunicode(msg)]
- commandlines = [cmdline]
- pushafter = self.repo.ui.config('tortoisehg', 'cipushafter')
- if pushafter:
- cmd = ['push', '--repository', self.repo.root, pushafter]
- commandlines.append(cmd)
- elif self.mergeChk.isChecked():
- cmdline += ['--merge']
- msg = self.msgTextEdit.text()
- cmdline += ['--message', hglib.fromunicode(msg)]
- commandlines = [cmdline]
+ def isComplete(self):
+ return len(self.msgEntry.text()) > 0
- # start backing out
- self.cmdline = cmdline
- self.repo.incrementBusyCount()
- self.cmd.run(*commandlines)
+ def validatePage(self):
+ if self.commitComplete:
+ # commit succeeded, repositoryChanged() called wizard().next()
+ if self.skiplast.isChecked():
+ self.wizard().close()
+ return True
+ if self.cmd.core.running():
+ return False
- def commit(self):
- cmdline = ['commit', '--repository', self.repo.root]
- msg = self.msgTextEdit.text()
- cmdline += ['--message='+hglib.fromunicode(msg)]
- self.cmdline = cmdline
+ if self.wizard().parentbackout:
+ self.setTitle(_('Backing out and committing...'))
+ self.setSubTitle(_('Please wait while making backout.'))
+ message = hglib.fromunicode(self.msgEntry.text())
+ cmdline = ['backout', '--verbose', '--message', message, '--rev',
+ str(self.wizard().backoutrev),
+ '--repository', self.repo.root]
+ else:
+ self.setTitle(_('Committing...'))
+ self.setSubTitle(_('Please wait while committing merged files.'))
+ message = hglib.fromunicode(self.msgEntry.text())
+ cmdline = ['commit', '--verbose', '--message', message,
+ '--repository', self.repo.root]
commandlines = [cmdline]
pushafter = self.repo.ui.config('tortoisehg', 'cipushafter')
if pushafter:
cmd = ['push', '--repository', self.repo.root, pushafter]
commandlines.append(cmd)
+
self.repo.incrementBusyCount()
+ self.cmd.setShowOutput(True)
self.cmd.run(*commandlines)
+ return False
- ### Signal Handlers ###
+ def onCommandFinished(self, ret):
+ self.repo.decrementBusyCount()
+ if ret == 0:
+ self.commitComplete = True
+ self.wizard().next()
- def cancel_clicked(self):
- self.cmd.cancel()
- def detail_toggled(self, checked):
- self.cmd.setShowOutput(checked)
+class ResultPage(BasePage):
+ def __init__(self, repo, parent):
+ super(ResultPage, self).__init__(repo, parent)
+ self.setTitle(_('Finished'))
+ self.setFinalPage(True)
- def command_started(self):
- self.cmd.setShown(True)
- self.mergeChk.setVisible(False)
- self.closeBtn.setHidden(True)
- self.cancelBtn.setShown(True)
- self.detailBtn.setShown(True)
- self.backoutBtn.setEnabled(False)
+ self.setLayout(QVBoxLayout())
+ sep = qtlib.LabeledSeparator(_('Backout changeset'))
+ self.layout().addWidget(sep)
+ bkCsInfo = csinfo.create(self.repo, 'tip', withupdate=True)
+ self.layout().addWidget(bkCsInfo)
+ self.bkCsInfo = bkCsInfo
+ self.layout().addStretch(1)
- def command_canceling(self):
- self.cancelBtn.setDisabled(True)
+ def currentPage(self):
+ self.bkCsInfo.update(self.repo['tip'])
+ self.wizard().setOption(QWizard.NoCancelButton, True)
- def command_finished(self, ret):
- self.repo.decrementBusyCount()
- self.cancelBtn.setHidden(True)
- # If the action wasn't successful, display the output and we're done
- if ret not in (0, 1):
- self.detailBtn.setChecked(True)
- self.closeBtn.setShown(True)
- self.closeBtn.setAutoDefault(True)
- self.closeBtn.setFocus()
- else:
- finished = True
- #If we backed out our parent, there is no second commit step
- if self.cmdline[0] == 'backout' and not self.backoutParent:
- finished = False
- self.msgTextEdit.setEnabled(True)
- self.backoutBtn.setEnabled(True)
- self.backoutBtn.setText(_('Commit', 'action button'))
- self.backoutBtn.clicked.disconnect(self.backout)
- self.backoutBtn.clicked.connect(self.commit)
- self.checkResolve()
+class CheckThread(QThread):
+ def __init__(self, repo, parent):
+ QThread.__init__(self, parent)
+ self.repo = hg.repository(repo.ui, repo.root)
+ self.results = (False, 1)
+ self.canceled = False
- if finished:
- if not self.cmd.outputShown():
- self.accept()
- else:
- self.closeBtn.clicked.disconnect(self.reject)
- self.closeBtn.clicked.connect(self.accept)
- self.closeBtn.setHidden(False)
+ def run(self):
+ self.repo.dirstate.invalidate()
+ unresolved = False
+ for root, path, status in thgrepo.recursiveMergeStatus(self.repo):
+ if self.canceled:
+ return
+ if status == 'u':
+ unresolved = True
+ break
+ wctx = self.repo[None]
+ dirty = bool(wctx.dirty()) or unresolved
+ self.results = (dirty, len(wctx.parents()))
- def checkResolve(self):
- for root, path, status in thgrepo.recursiveMergeStatus(self.repo):
- if status == 'u':
- txt = _('Backout generated merge <b>conflicts</b> that must '
- 'be <a href="resolve"><b>resolved</b></a>')
- self.backoutBtn.setEnabled(False)
- break
- else:
- self.backoutBtn.setEnabled(True)
- txt = _('You may commit the backed out changes after '
- '<a href="status"><b>verifying</b></a> them')
- self.reslabel.setText(txt)
+ def cancel(self):
+ self.canceled = True
- @pyqtSlot(QString)
- def link_activated(self, cmd):
- if cmd == 'resolve':
- dlg = resolve.ResolveDialog(self.repo, self)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- self.checkResolve()
- elif cmd == 'status':
- dlg = status.StatusDialog([], {}, self.repo.root, self)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- self.checkResolve()
-
- def accept(self):
- self.msgTextEdit.saveSettings(QSettings(), 'backout/message')
- super(BackoutDialog, self).accept()
def run(ui, *pats, **opts):
from tortoisehg.util import paths
repo = thgrepo.repository(ui, path=paths.find_root())
- kargs = {'opts': opts}
if opts.get('rev'):
- kargs['rev'] = opts.get('rev')
+ rev = opts.get('rev')
elif len(pats) == 1:
- kargs['rev'] = pats[0]
- return BackoutDialog(repo, **kargs)
+ rev = pats[0]
+ return BackoutDialog(rev, repo, None)
|
@@ -8,7 +8,7 @@ import os
import sys
-from mercurial import extensions, ui
+from mercurial import extensions
from tortoisehg.util import hglib, version
from tortoisehg.hgqt.i18n import _
@@ -147,6 +147,7 @@ self._textlabel = QLabel(text, wordWrap=True,
textInteractionFlags=labelflags)
self._textlabel.linkActivated.connect(self._openlink)
+ self._textlabel.setWordWrap(False)
self.layout().addWidget(self._textlabel)
bb = QDialogButtonBox(QDialogButtonBox.Close, centerButtons=True)
@@ -159,11 +160,20 @@ if ref == '#bugreport':
return BugReport(self._opts, self).exec_()
if ref.startswith('#edit:'):
- from tortoisehg.hgqt import wctxactions
fname, lineno = ref[6:].rsplit(':', 1)
- # A chicken-egg problem here, we need a ui to get your
- # editor in order to repair your ui config file.
- wctxactions.edit(self, ui.ui(), None, [fname], lineno, None)
+ try:
+ # A chicken-egg problem here, we need a ui to get your
+ # editor in order to repair your ui config file.
+ from mercurial import ui as uimod
+ from tortoisehg.hgqt import qtlib
+ class FakeRepo(object):
+ def __init__(self):
+ self.root = os.getcwd()
+ self.ui = uimod.ui()
+ fake = FakeRepo()
+ qtlib.editfiles(fake, [fname], lineno, parent=self)
+ except Exception, e:
+ QDesktopServices.openUrl(QUrl.fromLocalFile(fname))
def run(ui, *pats, **opts):
return BugReport(opts)
|
|
|
@@ -15,8 +15,8 @@ from tortoisehg.util import hglib
from tortoisehg.util.patchctx import patchctx
from tortoisehg.hgqt.i18n import _
-from tortoisehg.hgqt import qtlib, thgrepo, qscilib, lexers, wctxactions
-from tortoisehg.hgqt import filelistmodel, filelistview, fileview
+from tortoisehg.hgqt import qtlib, thgrepo, qscilib, lexers, visdiff, revert
+from tortoisehg.hgqt import filelistmodel, filelistview, filedata
from PyQt4.QtCore import *
from PyQt4.QtGui import *
@@ -36,10 +36,13 @@ fileModelEmpty = pyqtSignal(bool)
fileModified = pyqtSignal()
- def __init__(self, repo, parent):
+ contextmenu = None
+
+ def __init__(self, repo, parent, multiselectable):
QWidget.__init__(self, parent)
self.repo = repo
+ self.multiselectable = multiselectable
self.currentFile = None
layout = QVBoxLayout(self)
@@ -53,9 +56,12 @@ self.splitter.setChildrenCollapsible(False)
self.layout().addWidget(self.splitter)
- self.filelist = filelistview.HgFileListView(self)
- self.filelistmodel = filelistmodel.HgFileListModel(self.repo, self)
+ self.filelist = filelistview.HgFileListView(repo, self, multiselectable)
+ self.filelistmodel = filelistmodel.HgFileListModel(self)
self.filelist.setModel(self.filelistmodel)
+ self.filelist.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.filelist.customContextMenuRequested.connect(self.menuRequest)
+ self.filelist.doubleClicked.connect(self.vdiff)
self.fileListFrame = QFrame(self.splitter)
self.fileListFrame.setFrameShape(QFrame.NoFrame)
@@ -71,18 +77,71 @@ self.diffbrowse.linkActivated.connect(self.linkActivated)
self.diffbrowse.chunksSelected.connect(self.chunksSelected)
- self.filelist.fileRevSelected.connect(self.displayFile)
+ self.filelist.fileSelected.connect(self.displayFile)
self.filelist.clearDisplay.connect(self.diffbrowse.clearDisplay)
self.splitter.setStretchFactor(0, 0)
self.splitter.setStretchFactor(1, 3)
self.timerevent = self.startTimer(500)
+ self._actions = {}
+ for name, desc, icon, key, tip, cb in [
+ ('diff', _('Visual Diff'), 'visualdiff', 'Ctrl+D',
+ _('View file changes in external diff tool'), self.vdiff),
+ ('edit', _('Edit Local'), 'edit-file', 'Shift+Ctrl+E',
+ _('Edit current file in working copy'), self.editCurrentFile),
+ ('revert', _('Revert to Revision'), 'hg-revert', 'Alt+Ctrl+T',
+ _('Revert file(s) to contents at this revision'),
+ self.revertfile),
+ ]:
+ act = QAction(desc, self)
+ if icon:
+ act.setIcon(qtlib.getmenuicon(icon))
+ if key:
+ act.setShortcut(key)
+ if tip:
+ act.setStatusTip(tip)
+ if cb:
+ act.triggered.connect(cb)
+ self._actions[name] = act
+ self.addAction(act)
+
+ @pyqtSlot(QPoint)
+ def menuRequest(self, point):
+ actionlist = ['diff', 'edit', 'revert']
+ if not self.contextmenu:
+ menu = QMenu(self)
+ for act in actionlist:
+ menu.addAction(self._actions[act])
+ self.contextmenu = menu
+ self.contextmenu.exec_(self.filelist.mapToGlobal(point))
+
+ def vdiff(self):
+ filenames = self.getSelectedFiles()
+ if len(filenames) == 0:
+ return
+ opts = {'change':self.ctx.rev()}
+ dlg = visdiff.visualdiff(self.repo.ui, self.repo, filenames, opts)
+ if dlg:
+ dlg.exec_()
+ dlg.deleteLater()
+
+ def revertfile(self):
+ filenames = self.getSelectedFiles()
+ if len(filenames) == 0:
+ return
+ rev = self.ctx.rev()
+ if rev is None:
+ rev = self.ctx.p1().rev()
+ dlg = revert.RevertDialog(self.repo, filenames, rev, self)
+ dlg.exec_()
+ dlg.deleteLater()
+
def timerEvent(self, event):
'Periodic poll of currently displayed patch or working file'
- if not hasattr(self, 'filelistmodel'):
+ if not hasattr(self, 'filelist'):
return
- ctx = self.filelistmodel._ctx
+ ctx = self.ctx
if ctx is None:
return
if isinstance(ctx, patchctx):
@@ -142,12 +201,12 @@ return ok
def editCurrentFile(self):
- ctx = self.filelistmodel._ctx
+ ctx = self.ctx
if isinstance(ctx, patchctx):
- path = ctx._path
+ paths = [ctx._path]
else:
- path = self.repo.wjoin(self.currentFile)
- wctxactions.edit(self, self.repo.ui, self.repo, [path])
+ paths = self.getSelectedFiles()
+ qtlib.editfiles(self.repo, paths, parent=self)
def getSelectedFileAndChunks(self):
chunks = self.diffbrowse.curchunks
@@ -156,6 +215,9 @@ return self.currentFile, [chunks[0]] + dchunks
else:
return self.currentFile, []
+
+ def getSelectedFiles(self):
+ return self.filelist.getSelectedFiles()
def deleteSelectedChunks(self):
'delete currently selected chunks'
@@ -170,7 +232,7 @@ if not kchunks and qtlib.QuestionMsgBox(_('No chunks remain'),
_('Remove all file changes?')):
revertall = True
- ctx = self.filelistmodel._ctx
+ ctx = self.ctx
if isinstance(ctx, patchctx):
repo.thgbackup(ctx._path)
fp = util.atomictempfile(ctx._path, 'wb')
@@ -214,7 +276,7 @@ wlock = repo.wlock()
try:
repo.wopener(self.currentFile, 'wb').write(
- repo['.'][self.currentFile].data())
+ self.diffbrowse.origcontents)
fp = cStringIO.StringIO()
chunks[0].write(fp)
for c in kchunks:
@@ -234,7 +296,7 @@ return True
return False
repo = self.repo
- ctx = self.filelistmodel._ctx
+ ctx = self.ctx
if isinstance(ctx, patchctx):
if wfile in ctx._files:
patchchunks = ctx._files[wfile]
@@ -295,11 +357,11 @@ return False
def getFileList(self):
- return self.filelistmodel._ctx.files()
+ return self.ctx.files()
def removeFile(self, wfile):
repo = self.repo
- ctx = self.filelistmodel._ctx
+ ctx = self.ctx
if isinstance(ctx, patchctx):
repo.thgbackup(ctx._path)
fp = util.atomictempfile(ctx._path, 'wb')
@@ -326,7 +388,7 @@
def getChunksForFile(self, wfile):
repo = self.repo
- ctx = self.filelistmodel._ctx
+ ctx = self.ctx
if isinstance(ctx, patchctx):
if wfile in ctx._files:
return ctx._files[wfile]
@@ -347,8 +409,11 @@ else:
return []
- @pyqtSlot(object, object, object)
- def displayFile(self, file, rev, status):
+ @pyqtSlot(QString, QString)
+ def displayFile(self, file, status):
+ if isinstance(file, (unicode, QString)):
+ file = hglib.fromunicode(file)
+ status = hglib.fromunicode(status)
if file:
self.currentFile = file
path = self.repo.wjoin(file)
@@ -366,7 +431,7 @@
def setContext(self, ctx):
self.diffbrowse.setContext(ctx)
- self.filelistmodel.setContext(ctx)
+ self.filelist.setContext(ctx)
empty = len(ctx.files()) == 0
self.fileModelEmpty.emit(empty)
self.fileSelected.emit(not empty)
@@ -375,9 +440,12 @@ self.diffbrowse.clearDisplay()
self.diffbrowse.clearChunks()
self.diffbrowse.updateSummary()
+ self.ctx = ctx
+ for act in ['diff', 'revert']:
+ self._actions[act].setEnabled(ctx.rev() is None)
def refresh(self):
- ctx = self.filelistmodel._ctx
+ ctx = self.ctx
if isinstance(ctx, patchctx):
# if patch mtime has not changed, it could return the same ctx
ctx = self.repo.changectx(ctx._path)
@@ -385,8 +453,6 @@ self.repo.thginvalidate()
ctx = self.repo.changectx(ctx.node())
self.setContext(ctx)
- if self.currentFile:
- self.filelist.selectFile(self.currentFile)
def loadSettings(self, qs, prefix):
self.diffbrowse.loadSettings(qs, prefix)
@@ -583,7 +649,7 @@ self._lastfile = filename
self.clearChunks()
- fd = fileview.FileData(self._ctx, None, filename, status)
+ fd = filedata.FileData(self._ctx, None, filename, status)
if fd.elabel:
self.extralabel.setText(fd.elabel)
@@ -624,6 +690,7 @@ else:
self.sci.markerAdd(start+i, self.vertical)
start += len(chunk.lines) + 1
+ self.origcontents = fd.olddata
self.countselected = 0
self.curchunks = chunks
for c in chunks[1:]:
|
|
|
@@ -7,7 +7,7 @@ # 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
+import os, string
from PyQt4.QtCore import *
from PyQt4.QtGui import *
@@ -21,6 +21,7 @@class CloneDialog(QDialog):
cmdfinished = pyqtSignal(int)
+ clonedRepository = pyqtSignal(QString)
def __init__(self, args=None, opts={}, parent=None):
super(CloneDialog, self).__init__(parent)
@@ -100,19 +101,28 @@ optbox.setSpacing(6)
grid.addLayout(optbox, 2, 1, 1, 2)
- def chktext(chklabel, stretch=None):
+ def chktext(chklabel, btnlabel=None, btnslot=None, stretch=None):
hbox = QHBoxLayout()
hbox.setSpacing(0)
optbox.addLayout(hbox)
chk = QCheckBox(chklabel)
text = QLineEdit(enabled=False)
- chk.toggled.connect(
- lambda e: self.toggle_enabled(e, text))
hbox.addWidget(chk)
hbox.addWidget(text)
if stretch is not None:
hbox.addStretch(stretch)
- return chk, text
+ if btnlabel:
+ btn = QPushButton(btnlabel)
+ btn.setEnabled(False)
+ btn.setAutoDefault = False
+ btn.clicked.connect(btnslot)
+ hbox.addWidget(btn)
+ chk.toggled.connect(
+ lambda e: self.toggle_enabled(e, text, target2=btn))
+ return chk, text, btn
+ else:
+ chk.toggled.connect(lambda e: self.toggle_enabled(e, text))
+ return chk, text
self.rev_chk, self.rev_text = chktext(_('Clone to revision:'),
stretch=40)
@@ -120,11 +130,13 @@ self.noupdate_chk = QCheckBox(_('Do not update the new working directory'))
self.pproto_chk = QCheckBox(_('Use pull protocol to copy metadata'))
self.uncomp_chk = QCheckBox(_('Use uncompressed transfer'))
- self.qclone_chk = QCheckBox(_('Include patch queue'))
optbox.addWidget(self.noupdate_chk)
optbox.addWidget(self.pproto_chk)
optbox.addWidget(self.uncomp_chk)
- optbox.addWidget(self.qclone_chk)
+
+ self.qclone_chk, self.qclone_txt,self.qclone_btn = \
+ chktext(_('Include patch queue'), btnlabel=_('Browse...'),
+ btnslot=self.onBrowseQclone)
self.proxy_chk = QCheckBox(_('Use proxy server'))
optbox.addWidget(self.proxy_chk)
@@ -132,12 +144,24 @@ self.proxy_chk.setEnabled(useproxy)
self.proxy_chk.setChecked(useproxy)
+ self.insecure_chk = QCheckBox(_('Do not verify host certificate'))
+ optbox.addWidget(self.insecure_chk)
+ self.insecure_chk.setEnabled(False)
+
self.remote_chk, self.remote_text = chktext(_('Remote command:'))
# allow to specify start revision for p4 & svn repos.
self.startrev_chk, self.startrev_text = chktext(_('Start revision:'),
stretch=40)
+ self.hgcmd_lbl = QLabel(_('Hg command:'))
+ self.hgcmd_lbl.setAlignment(Qt.AlignRight)
+ self.hgcmd_txt = QLineEdit()
+ self.hgcmd_txt.setReadOnly(True)
+ grid.addWidget(self.hgcmd_lbl, 3, 0)
+ grid.addWidget(self.hgcmd_txt, 3, 1)
+ self.hgcmd_txt.setMinimumWidth(400)
+
## command widget
self.cmd = cmdui.Widget(True, True, self)
self.cmd.commandStarted.connect(self.command_started)
@@ -169,6 +193,23 @@ self.setWindowTitle(_('Clone - %s') % ucwd)
self.setWindowIcon(qtlib.geticon('hg-clone'))
+ # connect extra signals
+ self.src_combo.editTextChanged.connect(self.composeCommand)
+ self.src_combo.editTextChanged.connect(self.onUrlHttps)
+ self.dest_combo.editTextChanged.connect(self.composeCommand)
+ self.rev_chk.toggled.connect(self.composeCommand)
+ self.rev_text.textChanged.connect(self.composeCommand)
+ self.noupdate_chk.toggled.connect(self.composeCommand)
+ self.pproto_chk.toggled.connect(self.composeCommand)
+ self.uncomp_chk.toggled.connect(self.composeCommand)
+ self.qclone_chk.toggled.connect(self.composeCommand)
+ self.qclone_txt.textChanged.connect(self.composeCommand)
+ self.proxy_chk.toggled.connect(self.composeCommand)
+ self.insecure_chk.toggled.connect(self.composeCommand)
+ self.remote_chk.toggled.connect(self.composeCommand)
+ self.remote_text.textChanged.connect(self.composeCommand)
+ self.startrev_chk.toggled.connect(self.composeCommand)
+
# prepare to show
self.cmd.setHidden(True)
self.cancel_btn.setHidden(True)
@@ -186,11 +227,16 @@ self.src_combo.setFocus()
self.src_combo.lineEdit().selectAll()
+ self.composeCommand()
+
+ ### Private Methods ###
+
+ def getSrc(self):
+ return hglib.fromunicode(self.src_combo.currentText()).strip()
+
def getDest(self):
return hglib.fromunicode(self.dest_combo.currentText()).strip()
- ### Private Methods ###
-
def show_options(self, visible):
self.rev_chk.setVisible(visible)
self.rev_text.setVisible(visible)
@@ -198,12 +244,57 @@ self.pproto_chk.setVisible(visible)
self.uncomp_chk.setVisible(visible)
self.proxy_chk.setVisible(visible)
+ self.insecure_chk.setVisible(visible)
self.qclone_chk.setVisible(visible)
self.remote_chk.setVisible(visible)
self.remote_text.setVisible(visible)
self.startrev_chk.setVisible(visible and self.startrev_available())
self.startrev_text.setVisible(visible and self.startrev_available())
+ def composeCommand(self):
+ remotecmd = hglib.fromunicode(self.remote_text.text().trimmed())
+ rev = hglib.fromunicode(self.rev_text.text().trimmed())
+ startrev = hglib.fromunicode(self.startrev_text.text().trimmed())
+ if self.qclone_chk.isChecked():
+ qclonedir = hglib.fromunicode(self.qclone_txt.text().trimmed())
+ if qclonedir == '':
+ qclonedir = '.hg\patches'
+ self.qclone_txt.setText(qclonedir)
+ cmdline = ['qclone']
+ if not qclonedir in ['.hg\patches', '.hg/patches', '']:
+ cmdline += ['--patches', qclonedir]
+ else:
+ cmdline = ['clone']
+ if self.noupdate_chk.isChecked():
+ cmdline.append('--noupdate')
+ if self.uncomp_chk.isChecked():
+ cmdline.append('--uncompressed')
+ if self.pproto_chk.isChecked():
+ cmdline.append('--pull')
+ if self.ui.config('http_proxy', 'host'):
+ if not self.proxy_chk.isChecked():
+ cmdline += ['--config', 'http_proxy.host=']
+ if self.remote_chk.isChecked() and remotecmd:
+ cmdline.append('--remotecmd')
+ cmdline.append(remotecmd)
+ if self.rev_chk.isChecked() and rev:
+ cmdline.append('--rev')
+ cmdline.append(rev)
+ if self.startrev_chk.isChecked() and startrev:
+ cmdline.append('--startrev')
+ cmdline.append(startrev)
+ cmdline.append('--verbose')
+ src = self.getSrc()
+ dest = self.getDest()
+ if self.insecure_chk.isChecked() and src.startswith('https://'):
+ cmdline.append('--insecure')
+ cmdline.append(src)
+ if dest:
+ cmdline.append('--')
+ cmdline.append(dest)
+ self.hgcmd_txt.setText(hglib.tounicode(' '.join(['hg'] + cmdline)))
+ return cmdline
+
def startrev_available(self):
entry = cmdutil.findcmd('clone', commands.table)[1]
longopts = set(e[1] for e in entry[1])
@@ -247,10 +338,6 @@ s.setValue('clone/source', self.shist)
s.setValue('clone/dest', self.dhist)
- remotecmd = hglib.fromunicode(self.remote_text.text().trimmed())
- rev = hglib.fromunicode(self.rev_text.text().trimmed())
- startrev = hglib.fromunicode(self.startrev_text.text().trimmed())
-
# verify input
if src == '':
qtlib.ErrorMsgBox(_('TortoiseHg Clone'),
@@ -279,34 +366,9 @@ dest = os.path.join(os.path.dirname(dirabs), dest)
# prepare command line
- if self.qclone_chk.isChecked():
- cmdline = ['qclone']
- else:
- cmdline = ['clone']
- if self.noupdate_chk.isChecked():
- cmdline.append('--noupdate')
- if self.uncomp_chk.isChecked():
- cmdline.append('--uncompressed')
- if self.pproto_chk.isChecked():
- cmdline.append('--pull')
- if self.ui.config('http_proxy', 'host'):
- if not self.proxy_chk.isChecked():
- cmdline += ['--config', 'http_proxy.host=']
- if self.remote_chk.isChecked() and remotecmd:
- cmdline.append('--remotecmd')
- cmdline.append(remotecmd)
- if self.rev_chk.isChecked() and rev:
- cmdline.append('--rev')
- cmdline.append(rev)
- if self.startrev_chk.isChecked() and startrev:
- cmdline.append('--startrev')
- cmdline.append(startrev)
-
- cmdline.append('--verbose')
- cmdline.append(src)
- if dest:
- cmdline.append('--')
- cmdline.append(dest)
+ self.src_combo.setEditText(hglib.tounicode(src))
+ self.dest_combo.setEditText(hglib.tounicode(dest))
+ cmdline = self.composeCommand()
# do not make the same clone twice (see #514)
if dest == self.prev_dest and os.path.exists(dest):
@@ -321,10 +383,13 @@
### Signal Handlers ###
- def toggle_enabled(self, checked, target):
+ def toggle_enabled(self, checked, target, target2=None):
target.setEnabled(checked)
if checked:
target.setFocus()
+ if target2:
+ target2.setEnabled(checked)
+ self.composeCommand()
def detail_toggled(self, checked):
self.cmd.setShowOutput(checked)
@@ -337,6 +402,7 @@ if path:
self.src_combo.setEditText(QDir.toNativeSeparators(path))
self.dest_combo.setFocus()
+ self.composeCommand()
def browse_dest(self):
FD = QFileDialog
@@ -346,6 +412,26 @@ if path:
self.dest_combo.setEditText(QDir.toNativeSeparators(path))
self.dest_combo.setFocus()
+ self.composeCommand()
+
+ def onBrowseQclone(self):
+ FD = QFileDialog
+ caption = _("Select patch folder")
+ upath = FD.getExistingDirectory(self, caption, \
+ self.qclone_txt.text(), QFileDialog.ShowDirsOnly)
+ if upath:
+ path = hglib.fromunicode(upath).replace('/', os.sep)
+ src = hglib.fromunicode(self.src_combo.currentText())
+ if not path.startswith(src):
+ qtlib.ErrorMsgBox('TortoiseHg QClone',
+ _('The selected patch folder is not'
+ ' under the source repository.'),
+ '<p>src = %s</p><p>path = %s</p>' % (src, path))
+ return
+ path = path.replace(src + os.sep, '')
+ self.qclone_txt.setText(QDir.toNativeSeparators(hglib.tounicode(path)))
+ self.qclone_txt.setFocus()
+ self.composeCommand()
def command_started(self):
self.cmd.setShown(True)
@@ -366,12 +452,21 @@ else:
self.accept()
+ if not ret:
+ # Let the workbench know that a repository has been successfully
+ # cloned
+ self.clonedRepository.emit(self.dest_combo.currentText())
+
def onCloseClicked(self):
if self.ret is 0:
self.accept()
else:
self.reject()
+ def onUrlHttps(self):
+ self.insecure_chk.setEnabled(self.getSrc().startswith('https://'))
+ self.composeCommand()
+
def command_canceling(self):
self.cancel_btn.setDisabled(True)
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
@@ -131,7 +131,7 @@ descs = [self.repo[c].description() for c in revs]
self.repo.opener('cur-message.txt', 'w').write('\n* * *\n'.join(descs))
- dlg = commit.CommitDialog([], dict(root=self.repo.root), self)
+ dlg = commit.CommitDialog(self.repo, [], {}, self)
dlg.finished.connect(dlg.deleteLater)
dlg.exec_()
self.showMessage.emit(_('Compress is complete, old history untouched'))
@@ -141,7 +141,7 @@
def linkActivated(self, cmd):
if cmd == 'commit':
- dlg = commit.CommitDialog([], dict(root=self.repo.root), self)
+ dlg = commit.CommitDialog(self.repo, [], {}, self)
dlg.finished.connect(dlg.deleteLater)
dlg.exec_()
self.checkStatus()
|
@@ -223,7 +223,7 @@ raise UnknownItem(item)
if 'label' in custom and not kargs.get('usepreset', False):
try:
- return custom['label'](widget, item)
+ return custom['label'](widget, item, ctx)
except UnknownItem:
pass
try:
|
@@ -81,9 +81,9 @@ filename = os.path.basename(widget.target)
return filename, revid
raise csinfo.UnknownItem(item)
- def labelfunc(widget, item):
+ def labelfunc(widget, item, ctx):
if item in ('item', 'item_l'):
- if not isinstance(widget.ctx, patchctx):
+ if not isinstance(ctx, patchctx):
return _('Revision:')
return _('Patch:')
raise csinfo.UnknownItem(item)
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
@@ -27,23 +27,16 @@ """
Model used for listing (modified) files of a given Hg revision
"""
-
- contextChanged = pyqtSignal(object)
showMessage = pyqtSignal(QString)
- def __init__(self, repo, parent):
- """
- data is a HgHLRepo instance
- """
+ def __init__(self, parent):
QAbstractTableModel.__init__(self, parent)
- self.repo = repo
self._boldfont = parent.font()
self._boldfont.setBold(True)
self._ctx = None
self._files = []
self._filesdict = {}
self._fulllist = False
- self._secondParent = False
@pyqtSlot(bool)
def toggleFullFileList(self, value):
@@ -51,11 +44,6 @@ self.loadFiles()
self.layoutChanged.emit()
- @pyqtSlot(bool)
- def toggleSecondParent(self, value):
- self._secondParent = value
- self.layoutChanged.emit()
-
def __len__(self):
return len(self._files)
@@ -69,7 +57,6 @@ return self._files[row]['path']
def setContext(self, ctx):
- self.contextChanged.emit(ctx)
reload = False
if not self._ctx:
reload = True
@@ -88,18 +75,6 @@ row = index.row()
return self._files[row]['path']
- def revFromIndex(self, index):
- 'return revision for index. index is guarunteed to be valid'
- if len(self._ctx.parents()) < 2:
- return None
- row = index.row()
- data = self._files[row]
- if (data['wasmerged'] and self._secondParent) or \
- (data['parent'] == 1 and self._fulllist):
- return self._ctx.p2().rev()
- else:
- return self._ctx.p1().rev()
-
def dataFromIndex(self, index):
if not index.isValid() or index.row()>=len(self) or not self._ctx:
return None
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
@@ -70,7 +70,7 @@ opts[val.name] = s in filetypes
opts['checkall'] = True # pre-check all matching files
- stwidget = status.StatusWidget(pats, opts, repo.root, self)
+ stwidget = status.StatusWidget(repo, pats, opts, self)
layout.addWidget(stwidget, 1)
if self.command == 'revert':
|
@@ -219,7 +219,7 @@ dlg.exec_()
self.checkResolve()
elif cmd == 'commit':
- dlg = commit.CommitDialog([], dict(root=self.repo.root), self)
+ dlg = commit.CommitDialog(self.repo, [], {}, self)
dlg.finished.connect(dlg.deleteLater)
dlg.exec_()
self.destcsinfo.update(self.repo['.'])
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
@@ -15,8 +15,10 @@
from PyQt4.QtCore import *
-def label_func(widget, item):
+def label_func(widget, item, ctx):
if item == 'cset':
+ if type(ctx.rev()) is str:
+ return _('Patch:')
return _('Changeset:')
elif item == 'parents':
return _('Parent:')
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
|
|
@@ -7,9 +7,9 @@
import os
-from mercurial import ui, util, error
+from mercurial import ui, scmutil, util, error
-from tortoisehg.util import hglib, settings, paths, wconfig, i18n
+from tortoisehg.util import hglib, settings, paths, wconfig, i18n, bugtraq
from tortoisehg.hgqt.i18n import _
from tortoisehg.hgqt import qtlib, qscilib, thgrepo
@@ -198,6 +198,66 @@ return self.value() != self.curvalue
+class BugTraqConfigureEntry(QPushButton):
+ def __init__(self, parent=None, **opts):
+ QPushButton.__init__(self, parent, toolTip=opts['tooltip'])
+
+ self.opts = opts
+ self.curvalue = None
+ self.options = None
+
+ self.tracker = None
+ self.master = None
+ self.setText(opts['label'])
+ self.clicked.connect(self.on_clicked)
+
+ def on_clicked(self, checked):
+ parameters = self.options
+ self.options = self.tracker.show_options_dialog(parameters)
+
+ def master_updated(self):
+ self.setEnabled(False)
+ if self.master == None:
+ return
+ if self.master.value() == None:
+ return
+ if len(self.master.value()) == 0:
+ return
+
+ try:
+ setting = self.master.value().split(' ', 1)
+ trackerid = setting[0]
+ name = setting[1]
+ self.tracker = bugtraq.BugTraq(trackerid)
+ except:
+ # failed to load bugtraq module or parse the setting:
+ # swallow the error and leave the widget disabled
+ return
+
+ try:
+ self.setEnabled(self.tracker.has_options())
+ except Exception, e:
+ qtlib.ErrorMsgBox(_('Issue Tracker'),
+ _('Failed to load issue tracker: \'%s\': %s. '
+ % (name, e)),
+ parent=self)
+
+ ## common APIs for all edit widgets
+ def setValue(self, curvalue):
+ if self.master == None:
+ self.master = self.opts['master']
+ self.master.currentIndexChanged.connect(self.master_updated)
+ self.master_updated()
+ self.curvalue = curvalue
+ self.options = curvalue
+
+ def value(self):
+ return self.options
+
+ def isDirty(self):
+ return self.value() != self.curvalue
+
+
def genEditCombo(opts, defaults=[]):
opts['canedit'] = True
opts['defaults'] = defaults
@@ -234,6 +294,22 @@def genFontEdit(opts):
return FontEntry(**opts)
+def genBugTraqEdit(opts):
+ return BugTraqConfigureEntry(**opts)
+
+def findIssueTrackerPlugins():
+ plugins = bugtraq.get_issue_plugins_with_names()
+ names = [("%s %s" % (key[0], key[1])) for key in plugins]
+ return names
+
+def issuePluginVisible():
+ try:
+ # quick test to see if we're able to load the bugtraq module
+ test = bugtraq.BugTraq('')
+ return True
+ except:
+ return False
+
def findDiffTools():
return hglib.difftools(ui.ui())
@@ -247,16 +323,26 @@class _fi(object):
"""Information of each field"""
__slots__ = ('label', 'cpath', 'values', 'tooltip',
- 'restartneeded', 'globalonly')
+ 'restartneeded', 'globalonly',
+ 'master', 'visible')
def __init__(self, label, cpath, values, tooltip,
- restartneeded=False, globalonly=False):
+ restartneeded=False, globalonly=False,
+ master=None, visible=None):
self.label = label
self.cpath = cpath
self.values = values
self.tooltip = tooltip
self.restartneeded = restartneeded
self.globalonly = globalonly
+ self.master = master
+ self.visible = visible
+
+ def isVisible(self):
+ if self.visible == None:
+ return True
+ else:
+ return self.visible()
INFO = (
({'name': 'general', 'label': 'TortoiseHg', 'icon': 'thg_logo'}, (
@@ -284,7 +370,8 @@ 'See <a href="%s">OpenAtLine</a>'
% 'http://bitbucket.org/tortoisehg/thg/wiki/OpenAtLine')),
_fi(_('Shell'), 'tortoisehg.shell', genEditCombo,
- _('Specify your preferred terminal shell application')),
+ _('Specify your preferred terminal shell application'),
+ globalonly=True),
_fi(_('Immediate Operations'), 'tortoisehg.immediate', genEditCombo,
_('Space separated list of shell operations you would like '
'to be performed immediately, without user interaction. '
@@ -298,13 +385,12 @@ _('Specify the number of spaces that tabs expand to in various '
'TortoiseHg windows. '
'Default: 0, Not expanded')),
+ _fi(_('Force Repo Tab'), 'tortoisehg.forcerepotab', genBoolCombo,
+ _('Always show repo tabs, even for a single repo. Default: False')),
_fi(_('Max Diff Size'), 'tortoisehg.maxdiff', genIntEditCombo,
_('The maximum size file (in KB) that TortoiseHg will '
'show changes for in the changelog, status, and commit windows. '
'A value of zero implies no limit. Default: 1024 (1MB)')),
- _fi(_('Capture stderr'), 'tortoisehg.stderrcapt', genBoolCombo,
- _('Redirect stderr to a buffer which is parsed at the end of '
- 'the process for runtime errors. Default: True')),
_fi(_('Fork GUI'), 'tortoisehg.guifork', genBoolCombo,
_('When running from the command line, fork a background '
'process to run graphical dialogs. Default: True')),
@@ -555,6 +641,13 @@ 'while {1} refers to the first group and so on. If no {n} tokens'
'are found in issue.link, the entire matched string is appended '
'instead.')),
+ _fi(_('Issue Tracker Plugin'), 'tortoisehg.issue.bugtraqplugin',
+ (genDeferredCombo, findIssueTrackerPlugins),
+ _('Configures a COM IBugTraqProvider or IBugTrackProvider2 issue '
+ 'tracking plugin.'), visible=issuePluginVisible),
+ _fi(_('Configure Issue Tracker'), 'tortoisehg.issue.bugtraqparameters', genBugTraqEdit,
+ _('Configure the selected COM Bug Tracker plugin.'),
+ master='tortoisehg.issue.bugtraqplugin', visible=issuePluginVisible),
)),
({'name': 'reviewboard', 'label': _('Review Board'), 'icon': 'reviewboard'}, (
@@ -610,7 +703,7 @@
self.conftabs = QTabWidget()
layout.addWidget(self.conftabs)
- utab = SettingsForm(rcpath=util.user_rcpath(), focus=focus)
+ utab = SettingsForm(rcpath=scmutil.user_rcpath(), focus=focus)
self.conftabs.addTab(utab, qtlib.geticon('settings_user'),
_("%s's global settings") % username())
utab.restartRequested.connect(self._pushRestartRequest)
@@ -729,13 +822,13 @@ tophbox.addWidget(reload)
bothbox = QHBoxLayout()
- layout.addLayout(bothbox)
+ layout.addLayout(bothbox, stretch=8)
pageList = QListWidget()
pageList.setResizeMode(QListView.Fixed)
stack = QStackedWidget()
bothbox.addWidget(pageList, 0)
bothbox.addWidget(stack, 1)
- pageList.currentRowChanged.connect(stack.setCurrentIndex)
+ pageList.currentRowChanged.connect(self.activatePage)
self.pages = {}
self.stack = stack
@@ -743,7 +836,7 @@
desctext = QTextBrowser()
desctext.setOpenExternalLinks(True)
- layout.addWidget(desctext)
+ layout.addWidget(desctext, stretch=2)
self.desctext = desctext
self.settings = QSettings()
@@ -758,11 +851,26 @@ icon.addPixmap(style.standardPixmap(meta['icon']))
item = QListWidgetItem(icon, meta['label'])
pageList.addItem(item)
- self.addPage(meta['name'])
self.refresh()
self.focusField(focus or 'ui.merge')
+ def activatePage(self, index):
+ item = self.pageList.currentItem()
+ for data in INFO:
+ if item.text() == data[0]['label']:
+ meta, info = data
+ break
+
+ pagename = meta['name']
+ if self.pages.has_key(pagename):
+ page = self.pages[pagename]
+ else:
+ page = self.createPage(pagename, info)
+ self.refreshPage(page)
+ frame = page[2][0].parentWidget()
+ self.stack.setCurrentWidget(frame)
+
def editClicked(self):
'Open internal editor in stacked widget'
if self.isDirty():
@@ -784,36 +892,40 @@ and os.access(self.fn, os.W_OK))
self.stack.setDisabled(self.readonly)
self.fnedit.setText(hglib.tounicode(self.fn))
- for name, info, widgets in self.pages.values():
- if name == 'extensions':
- extsmentioned = False
- for row, w in enumerate(widgets):
- key = w.opts['label']
- for fullkey in (key, 'hgext.%s' % key, 'hgext/%s' % key):
- val = self.readCPath('extensions.' + fullkey)
- if val != None:
- break
- if val == None:
- curvalue = False
- elif len(val) and val[0] == '!':
- curvalue = False
- extsmentioned = True
- else:
- curvalue = True
- extsmentioned = True
- w.setValue(curvalue)
- if val == None:
- w.opts['cpath'] = 'extensions.' + key
- else:
- w.opts['cpath'] = 'extensions.' + fullkey
- if not extsmentioned:
- # make sure widgets are shown properly,
- # even when no extensions mentioned in the config file
- self.validateextensions()
- else:
- for row, e in enumerate(info):
- curvalue = self.readCPath(e.cpath)
- widgets[row].setValue(curvalue)
+ for page in self.pages.values():
+ self.refreshPage(page)
+
+ def refreshPage(self, page):
+ name, info, widgets = page
+ if name == 'extensions':
+ extsmentioned = False
+ for row, w in enumerate(widgets):
+ key = w.opts['label']
+ for fullkey in (key, 'hgext.%s' % key, 'hgext/%s' % key):
+ val = self.readCPath('extensions.' + fullkey)
+ if val != None:
+ break
+ if val == None:
+ curvalue = False
+ elif len(val) and val[0] == '!':
+ curvalue = False
+ extsmentioned = True
+ else:
+ curvalue = True
+ extsmentioned = True
+ w.setValue(curvalue)
+ if val == None:
+ w.opts['cpath'] = 'extensions.' + key
+ else:
+ w.opts['cpath'] = 'extensions.' + fullkey
+ if not extsmentioned:
+ # make sure widgets are shown properly,
+ # even when no extensions mentioned in the config file
+ self.validateextensions()
+ else:
+ for row, e in enumerate(info):
+ curvalue = self.readCPath(e.cpath)
+ widgets[row].setValue(curvalue)
def isDirty(self):
if self.readonly:
@@ -853,7 +965,7 @@
for e in info:
opts = {'label': e.label, 'cpath': e.cpath, 'tooltip': e.tooltip,
- 'settings':self.settings}
+ 'master': e.master, 'settings':self.settings}
if isinstance(e.values, tuple):
func = e.values[0]
w = func(opts, e.values[1])
@@ -862,12 +974,20 @@ w = func(opts)
w.installEventFilter(self)
if e.globalonly:
- w.setEnabled(self.rcpath == util.user_rcpath())
+ w.setEnabled(self.rcpath == scmutil.user_rcpath())
lbl = QLabel(e.label)
lbl.installEventFilter(self)
lbl.setToolTip(e.tooltip)
- form.addRow(lbl, w)
widgets.append(w)
+ if e.isVisible():
+ form.addRow(lbl, w)
+
+ # assign the master to widgets that have a master
+ for w in widgets:
+ if w.opts['master'] != None:
+ for dep in widgets:
+ if dep.opts['cpath'] == w.opts['master']:
+ w.opts['master'] = dep
return widgets
def fillExtensionsFrame(self):
@@ -900,17 +1020,14 @@ return True # tooltip is shown in self.desctext
return False
- def addPage(self, name):
- for data in INFO:
- if name == data[0]['name']:
- meta, info = data
- break
+ def createPage(self, name, info):
if name == 'extensions':
extsinfo, widgets = self.fillExtensionsFrame()
self.pages[name] = name, extsinfo, widgets
else:
widgets = self.fillFrame(info)
self.pages[name] = name, info, widgets
+ return self.pages[name]
def readCPath(self, cpath):
'Retrieve a value from the parsed config file'
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
Loading...