diff --git a/tortoisehg/hgqt/repowidget.py b/tortoisehg/hgqt/repowidget.py
--- a/tortoisehg/hgqt/repowidget.py
+++ b/tortoisehg/hgqt/repowidget.py
@@ -18,7 +18,7 @@
from tortoisehg.hgqt.qtlib import CustomPrompt
from tortoisehg.hgqt.repomodel import HgRepoListModel
from tortoisehg.hgqt import cmdui, update, tag, backout, merge
-from tortoisehg.hgqt import archive, thgstrip, run
+from tortoisehg.hgqt import archive, thgimport, thgstrip, run
from tortoisehg.hgqt.repoview import HgRepoView
from tortoisehg.hgqt.revdetailswidget import RevDetailsWidget
@@ -128,6 +128,10 @@
def forward(self):
self.repoview.forward()
+ def thgimport(self):
+ dlg = thgimport.ImportDialog(repo=self.repo, parent=self)
+ dlg.exec_()
+
def verify(self):
cmdline = ['--repository', self.repo.root, 'verify']
dlg = cmdui.Dialog(cmdline, self)
diff --git a/tortoisehg/hgqt/workbench.py b/tortoisehg/hgqt/workbench.py
--- a/tortoisehg/hgqt/workbench.py
+++ b/tortoisehg/hgqt/workbench.py
@@ -255,6 +255,7 @@
self.actionShowLog = a
self.actionServe = QAction(_("Web Server"), self)
+ self.actionImport = QAction(_("Import"), self)
self.actionVerify = QAction(_("Verify"), self)
self.actionRecover = QAction(_("Recover"), self)
self.actionRollback = QAction(_("Rollback/Undo"), self)
@@ -286,6 +287,8 @@
self.menuRepository = m = QMenu(_("&Repository"), self.menubar)
m.addAction(self.actionServe)
m.addSeparator()
+ m.addAction(self.actionImport)
+ m.addSeparator()
m.addAction(self.actionVerify)
m.addAction(self.actionRecover)
m.addSeparator()
@@ -579,6 +582,7 @@
self.actionQuit.triggered.connect(self.close)
self.actionBack.triggered.connect(self.back)
self.actionForward.triggered.connect(self.forward)
+ self.actionImport.triggered.connect(self.thgimport)
self.actionLoadAll.triggered.connect(self.loadall)
self.actionSelectColumns.triggered.connect(self.setHistoryColumns)
self.actionShowPaths.toggled.connect(self.actionShowPathsToggled)
@@ -683,6 +687,11 @@
from tortoisehg.hgqt import run
run.serve(self.ui, root=w.repo.root)
+ def thgimport(self):
+ w = self.repoTabsWidget.currentWidget()
+ if w:
+ w.thgimport()
+
def verify(self):
w = self.repoTabsWidget.currentWidget()
if w:
Only the file browser and the p0 option are implemented so far.
Still needed:
- directory browser
- mq option
- import from clipboard
diff --git a/tortoisehg/hgqt/run.py b/tortoisehg/hgqt/run.py
--- a/tortoisehg/hgqt/run.py
+++ b/tortoisehg/hgqt/run.py
@@ -593,6 +593,11 @@
from tortoisehg.hgqt.thgstrip import run
qtrun(run, ui, *pats, **opts)
+def thgimport(ui, *pats, **opts):
+ """import an ordered set of patches"""
+ from tortoisehg.hgqt.thgimport import run
+ qtrun(run, ui, *pats, **opts)
+
### help management, adapted from mercurial.commands.help_()
def help_(ui, name=None, with_version=False, **opts):
"""show help for a command, extension, or list of commands
@@ -850,6 +855,10 @@
"^grep|search": (grep, [], _('thg grep')),
"^guess": (guess, [], _('thg guess')),
"^hgignore|ignore|filter": (hgignore, [], _('thg hgignore [FILE]')),
+ "import": (thgimport,
+ [('', 'repo', False, _('import to the repository')),
+ ('', 'mq', False, _('import to the patch queue (MQ)'))],
+ _('thg import [OPTION] [SOURCE]...')),
"^init": (init, [], _('thg init [DEST]')),
"^email":
(email,
diff --git a/tortoisehg/hgqt/thgimport.py b/tortoisehg/hgqt/thgimport.py
new file mode 100644
--- /dev/null
+++ b/tortoisehg/hgqt/thgimport.py
@@ -0,0 +1,192 @@
+# thgimport.py - Import dialog for TortoiseHg
+#
+# Copyright 2009 Yuki KODAMA <endfl...@gmail.com>
+# Copyright 2010 David Wilhelm <da...@jumbledpile.com>
+#
+# 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 hg, ui, error
+
+from tortoisehg.util import hglib, paths, thgrepo
+from tortoisehg.hgqt.i18n import _
+from tortoisehg.hgqt import cmdui, cslist, qtlib
+
+_FILE_SEP = ";"
+_FILE_FILTER = "%s;;%s" % (_("Patch files (*.diff *.patch)"),
+ _("All files (*)"))
+
+class ImportDialog(QDialog):
+ """Dialog to import patches"""
+
+ repoInvalidated = pyqtSignal()
+
+ def __init__(self, repo=None, rev=None, parent=None, opts={}):
+ super(ImportDialog, self).__init__(parent)
+ self.setWindowFlags(self.windowFlags() &
+ ~Qt.WindowContextHelpButtonHint)
+
+ self.ui = ui.ui()
+ if repo:
+ self.repo = repo
+ else:
+ root = paths.find_root()
+ if root:
+ self.repo = thgrepo.repository(self.ui, path=root)
+ else:
+ raise 'not repository'
+
+ # base layout box
+ box = QVBoxLayout()
+ box.setSpacing(6)
+
+ ## main layout grid
+ self.grid = grid = QGridLayout()
+ grid.setSpacing(6)
+ grid.setColumnStretch(0, 0)
+ grid.setColumnStretch(1, 1)
+ box.addLayout(grid)
+
+ ### source input
+ self.src_combo = QComboBox()
+ self.src_combo.setEditable(True)
+ self.src_combo.setMinimumWidth(310)
+ self.file_btn = QPushButton(_('Browse...'))
+ self.file_btn.setAutoDefault(False)
+ self.connect(self.file_btn, SIGNAL("clicked()"), self.browsefiles)
+ grid.addWidget(QLabel(_('Source:')), 0, 0)
+ grid.addWidget(self.src_combo, 0, 1)
+ grid.addWidget(self.file_btn, 0, 2)
+ self.p0chk = QCheckBox(_('Do not strip paths (-p0), '
+ 'required for SVN patches'))
+ grid.addWidget(self.p0chk, 1, 1, Qt.AlignLeft)
+ grid.addWidget(QLabel(_('Preview:')), 2, 0, Qt.AlignLeft | Qt.AlignTop)
+ self.status = QLabel("")
+ grid.addWidget(self.status, 2, 1, Qt.AlignLeft | Qt.AlignTop)
+
+ ### patch list
+ self.cslist = cslist.ChangesetList()
+ grid.addWidget(self.cslist, 3, 1, Qt.AlignLeft | Qt.AlignTop)
+
+ ## command widget
+ self.cmd = cmdui.Widget()
+ 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)
+
+ ## bottom buttons
+ buttons = QDialogButtonBox()
+ self.cancel_btn = buttons.addButton(QDialogButtonBox.Cancel)
+ self.cancel_btn.clicked.connect(self.cancel_clicked)
+ self.close_btn = buttons.addButton(QDialogButtonBox.Close)
+ self.close_btn.clicked.connect(self.reject)
+ self.close_btn.setAutoDefault(False)
+ self.import_btn = buttons.addButton(_('&Import'),
+ QDialogButtonBox.ActionRole)
+ self.import_btn.clicked.connect(self.thgimport)
+ self.detail_btn = buttons.addButton(_('Detail'),
+ QDialogButtonBox.ResetRole)
+ self.detail_btn.setAutoDefault(False)
+ self.detail_btn.setCheckable(True)
+ self.detail_btn.toggled.connect(self.detail_toggled)
+ box.addWidget(buttons)
+
+ # signal handlers
+ self.src_combo.editTextChanged.connect(lambda *a: self.preview())
+ self.src_combo.lineEdit().returnPressed.connect(self.thgimport)
+ self.p0chk.toggled.connect(lambda *a: self.preview())
+
+ # dialog setting
+ self.setLayout(box)
+ self.layout().setSizeConstraint(QLayout.SetMinAndMaxSize)
+ reponame = hglib.get_reponame(self.repo)
+ self.setWindowTitle(_('Import - %s') % hglib.tounicode(reponame))
+ #self.setWindowIcon(qtlib.geticon('import'))
+
+ # prepare to show
+ self.src_combo.lineEdit().selectAll()
+ self.cslist.setHidden(False)
+ self.cmd.setHidden(True)
+ self.cancel_btn.setHidden(True)
+ self.detail_btn.setHidden(True)
+ self.p0chk.setHidden(False)
+ self.preview()
+
+ ### Private Methods ###
+
+ def browsefiles(self):
+ caption = _("Select patches")
+ path = QFileDialog.getOpenFileNames(parent=self, caption=caption,
+ filter=_FILE_FILTER)
+ if path:
+ response = _FILE_SEP.join([str(x) for x in path])
+ self.src_combo.setEditText(response)
+ self.src_combo.setFocus()
+
+ def updatestatus(self):
+ items = self.cslist.curitems
+ count = items and len(items) or 0
+ countstr = _("%s patches") % count
+ if count:
+ self.status.setText(_('%s will be imported to the repository') %
+ countstr)
+ else:
+ self.status.setText(_('Nothing to import'))
+
+ def preview(self):
+ patches = hglib.fromunicode(self.src_combo.currentText())
+ if not patches:
+ self.cslist.clear()
+ self.import_btn.setDisabled(True)
+ else:
+ patches = patches.split(_FILE_SEP)
+ self.cslist.update(self.repo, patches)
+ self.import_btn.setEnabled(True)
+ self.updatestatus()
+
+ def thgimport(self):
+ hgcmd = 'import'
+ cmdline = [hgcmd, '--repository', self.repo.root]
+ if self.p0chk.isChecked():
+ cmdline.append('-p0')
+ cmdline.extend(['--verbose', '--'])
+ cmdline.extend(self.cslist.curitems)
+
+ self.cmd.run(cmdline)
+
+ ### Signal Handlers ###
+
+ def cancel_clicked(self):
+ self.cmd.cancel()
+ self.reject()
+
+ def detail_toggled(self, checked):
+ self.cmd.show_output(checked)
+
+ def command_started(self):
+ self.cmd.setShown(True)
+ self.import_btn.setHidden(True)
+ self.close_btn.setHidden(True)
+ self.cancel_btn.setShown(True)
+ self.detail_btn.setShown(True)
+
+ def command_finished(self, wrapper):
+ if wrapper.data is not 0 or self.cmd.is_show_output():
+ self.detail_btn.setChecked(True)
+ self.close_btn.setShown(True)
+ self.close_btn.setAutoDefault(True)
+ self.close_btn.setFocus()
+ self.cancel_btn.setHidden(True)
+ else:
+ self.repoInvalidated.emit()
+ self.accept()
+
+ def command_canceling(self):
+ self.cancel_btn.setDisabled(True)
+
+def run(ui, *pats, **opts):
+ return ImportDialog(opts=opts)
diff --git a/tortoisehg/hgqt/thgimport.py b/tortoisehg/hgqt/thgimport.py
--- a/tortoisehg/hgqt/thgimport.py
+++ b/tortoisehg/hgqt/thgimport.py
@@ -130,12 +130,14 @@
def updatestatus(self):
items = self.cslist.curitems
count = items and len(items) or 0
- countstr = _("%s patches") % count
+ countstr = qtlib.markup(_("%s patches") % count, weight='bold')
if count:
self.status.setText(_('%s will be imported to the repository') %
countstr)
else:
- self.status.setText(_('Nothing to import'))
+ text = qtlib.markup(_('Nothing to import'), weight='bold',
+ fg='red')
+ self.status.setText(text)
def preview(self):
patches = hglib.fromunicode(self.src_combo.currentText())
diff --git a/tortoisehg/hgqt/thgstrip.py b/tortoisehg/hgqt/thgstrip.py
--- a/tortoisehg/hgqt/thgstrip.py
+++ b/tortoisehg/hgqt/thgstrip.py
@@ -51,8 +51,8 @@
grid.addWidget(QLabel(_('Strip:')), 0, 0)
grid.addWidget(combo, 0, 1)
grid.addWidget(QLabel(_('Preview:')), 1, 0, Qt.AlignLeft | Qt.AlignTop)
- self.resultlbl = QLabel("")
- grid.addWidget(self.resultlbl, 1, 1, Qt.AlignLeft | Qt.AlignTop)
+ self.status = QLabel("")
+ grid.addWidget(self.status, 1, 1, Qt.AlignLeft | Qt.AlignTop)
if rev is None:
rev = self.repo.dirstate.branch()
@@ -162,12 +162,15 @@
def preview(self):
if self.updatecslist():
striprevs = self.cslist.curitems
- self.resultlbl.setText(_("%s will be stripped") %
- _("%s changesets") % len(striprevs))
+ cstext = qtlib.markup(_("%s changesets") % len(striprevs),
+ weight='bold')
+ self.status.setText(_("%s will be stripped") % cstext)
self.strip_btn.setEnabled(True)
else:
self.cslist.clear()
- self.resultlbl.setText(_('unknown revision!'))
+ cstext = qtlib.markup(_('Unknown revision!'), fg='red',
+ weight='bold')
+ self.status.setText(cstext)
self.strip_btn.setDisabled(True)
def strip(self):
diff --git a/tortoisehg/hgqt/thgstrip.py b/tortoisehg/hgqt/thgstrip.py
--- a/tortoisehg/hgqt/thgstrip.py
+++ b/tortoisehg/hgqt/thgstrip.py
@@ -13,7 +13,7 @@
from tortoisehg.util import hglib, paths, thgrepo
from tortoisehg.hgqt.i18n import _
-from tortoisehg.hgqt import cmdui, csinfo, qtlib
+from tortoisehg.hgqt import cmdui, cslist, qtlib
class StripDialog(QDialog):
"""Dialog to strip changesets"""
@@ -24,7 +24,6 @@
super(StripDialog, self).__init__(parent)
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
- self.limit = 20
self.ui = ui.ui()
if repo:
self.repo = repo
@@ -71,20 +70,8 @@
combo.addItem(hglib.tounicode(tag))
### preview box, contained in scroll area, contains preview grid
- self.scrollarea = QScrollArea()
- self.scrollarea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
- self.scrollarea.setWidgetResizable(True)
- self.previewbox = QWidget()
- self.previewbox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
- self.previewgrid = QVBoxLayout()
-
- #### preview layout grid, contains Factory objects (one per revision)
- self.previewgrid.setSpacing(6)
- self.previewgrid.setSizeConstraint(QLayout.SetMinAndMaxSize)
- self.previewbox.setLayout(self.previewgrid)
-
- self.scrollarea.setWidget(self.previewbox)
- grid.addWidget(self.scrollarea, 2, 1, Qt.AlignLeft | Qt.AlignTop)
+ self.cslist = cslist.ChangesetList()
+ grid.addWidget(self.cslist, 2, 1, Qt.AlignLeft | Qt.AlignTop)
### options
optbox = QVBoxLayout()
@@ -127,9 +114,9 @@
box.addWidget(buttons)
# signal handlers
- self.rev_combo.editTextChanged.connect(lambda *a: self.strip_info())
+ self.rev_combo.editTextChanged.connect(lambda *a: self.preview())
self.rev_combo.lineEdit().returnPressed.connect(self.strip)
- self.discard_chk.toggled.connect(lambda *a: self.strip_info())
+ self.discard_chk.toggled.connect(lambda *a: self.preview())
# dialog setting
self.setLayout(box)
@@ -140,56 +127,46 @@
# prepare to show
self.rev_combo.lineEdit().selectAll()
- self.previewbox.setHidden(False)
+ self.cslist.setHidden(False)
self.cmd.setHidden(True)
self.cancel_btn.setHidden(True)
self.detail_btn.setHidden(True)
self.nobackup_chk.setHidden(True)
- self.strip_info()
+ self.preview()
### Private Methods ###
- def clear_preview(self):
- while self.previewgrid.count():
- w = self.previewgrid.takeAt(0).widget()
- w.deleteLater()
+ def get_rev(self):
+ """Return the integer revision number of the input or None"""
+ revstr = hglib.fromunicode(self.rev_combo.currentText())
+ if not revstr:
+ return None
+ try:
+ rev = self.repo[revstr].rev()
+ except (error.RepoError, error.LookupError):
+ return None
+ return rev
- def preview_updated(self, rev, uselimit=True):
- items = ('%(rev)s', ' %(branch)s', ' %(tags)s', ' %(summary)s')
- style = csinfo.labelstyle(contents=items, width=350, selectable=True)
- factory = csinfo.factory(self.repo, style=style)
- parctxs = self.repo[None].parents()
+ def updatecslist(self, uselimit=True):
+ """Update the cs list and return the success status as a bool"""
+ rev = self.get_rev()
+ if not rev:
+ return False
striprevs = list(self.repo.changelog.descendants(rev))
striprevs.append(rev)
striprevs.sort()
- self.resultlbl.setText(_("%s will be stripped") % _("%s changesets")
- % len(striprevs))
- if uselimit and len(striprevs) > self.limit:
- showrevs = striprevs[:self.limit - 1] + [striprevs[-1]]
- addsnip = True
+ self.cslist.clear()
+ self.cslist.update(self.repo, striprevs)
+ return True
+
+ def preview(self):
+ if self.updatecslist():
+ striprevs = self.cslist.curitems
+ self.resultlbl.setText(_("%s will be stripped") %
+ _("%s changesets") % len(striprevs))
+ self.strip_btn.setEnabled(True)
else:
- showrevs = striprevs
- addsnip = False
- self.clear_preview()
- for showrev in showrevs:
- info = factory()
- info.update(self.repo[showrev])
- if showrev == showrevs[-1] and addsnip:
- self.previewgrid.addWidget(QLabel("..."))
- self.previewgrid.addWidget(info, Qt.AlignTop)
-
- def strip_info(self):
- revstr = hglib.fromunicode(self.rev_combo.currentText())
- if not revstr:
- self.clear_preview()
- self.resultlbl.setText(_('unknown revision!'))
- self.strip_btn.setDisabled(True)
- return
- try:
- self.preview_updated(self.repo[revstr].rev())
- self.strip_btn.setEnabled(True)
- except (error.LookupError, error.RepoLookupError, error.RepoError):
- self.clear_preview()
+ self.cslist.clear()
self.resultlbl.setText(_('unknown revision!'))
self.strip_btn.setDisabled(True)
diff --git a/tortoisehg/hgqt/cslist.py b/tortoisehg/hgqt/cslist.py
new file mode 100644
--- /dev/null
+++ b/tortoisehg/hgqt/cslist.py
@@ -0,0 +1,95 @@
+# cslist.py - embeddable changeset/patch list component
+#
+# Copyright 2009 Yuki KODAMA <endfl...@gmail.com>
+# Copyright 2010 David Wilhelm <da...@jumbledpile.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2, incorporated herein by reference.
+
+from mercurial import hg
+
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+from tortoisehg.hgqt import csinfo
+from tortoisehg.hgqt.i18n import _
+
+_SPACING = 6
+
+class ChangesetList(QScrollArea):
+
+ def __init__(self, parent=None):
+ super(ChangesetList, self).__init__()
+
+ self.currepo = None
+ self.curitems = None
+ self.showitems = None
+ self.limit = 20
+
+ # scroll area
+ self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+ self.setWidgetResizable(True)
+ self.setMinimumSize(400, 200)
+
+ # main layout
+ self.csmain = QWidget()
+ self.csmain.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+ self.setWidget(self.csmain)
+
+ ## cs layout grid, contains Factory objects, one per revision
+ self.csvbox = QVBoxLayout()
+ self.csvbox.setSpacing(_SPACING)
+ self.csvbox.setSizeConstraint(QLayout.SetMinAndMaxSize)
+ self.csmain.setLayout(self.csvbox)
+
+ # add the main layout
+ self.setWidget(self.csmain)
+
+ def clear(self):
+ """Clear the item list"""
+ while self.csvbox.count():
+ w = self.csvbox.takeAt(0).widget()
+ w.deleteLater()
+ self.curitems = None
+
+ def update(self, repo, items, uselimit=True):
+ """Update the item list.
+
+ Public arguments:
+ repo: Repository used to get changeset information.
+ items: List of revision numbers and/or patch file paths.
+ You can pass a mixed list. The order will be respected.
+ uselimit: If True, some of items will be shown.
+
+ return: True if the item list was updated successfully,
+ False if it wasn't updated.
+ """
+ # setup
+ self.clear()
+ contents = ('%(rev)s', ' %(branch)s', ' %(tags)s', ' %(summary)s')
+ style = csinfo.labelstyle(contents=contents, width=350,
+ selectable=True)
+ factory = csinfo.factory(repo, style=style)
+
+ # initialize variables
+ self.currepo = repo
+ self.curitems = items
+
+ # determine the items to show
+ if uselimit and self.limit < len(items):
+ showitems, lastitem = items[:self.limit - 1], items[-1]
+ else:
+ showitems, lastitem = items, None
+ numshow = len(showitems) + (lastitem and 1 or 0)
+ self.showitems = showitems + (lastitem and [lastitem] or [])
+
+ # show items
+ for item in showitems:
+ info = factory()
+ info.update(item)
+ self.csvbox.addWidget(info, Qt.AlignTop)
+ if lastitem:
+ self.csvbox.addWidget(QLabel("..."))
+ info = factory()
+ info.update(lastitem)
+ self.csvbox.addWidget(info, Qt.AlignTop)
+
Looks good, pushed, thanks.
FWIW: if you implement the clipboard import first, I'll quit using the
hgtk version immediately.
--
Steve Borho
OK, good to know. Thanks.