Requires rupdate: http://bitbucket.org/MrWerewolf/rupdate
Remote Update is adds the ability to update a remote repository to a
specified revision if the remote repository is accessed through ssh.
Note: The default TortoisePlink.exe will give you a "Not enough storage is
available to process this command" error when you try to run this. To get around
this, use another version of plink. I use a copy of the TortoisePlink.exe from
TortoiseCVS.
diff --git a/tortoisehg/hgqt/repowidget.py b/tortoisehg/hgqt/repowidget.py
--- a/tortoisehg/hgqt/repowidget.py
+++ b/tortoisehg/hgqt/repowidget.py
@@ -1010,6 +1010,9 @@
entry(menu, 'reviewboard', fixed, _('Post to Review Board...'), 'reviewboard',
self.sendToReviewBoard)
+ entry(menu, 'rupdate', fixed, _('Remote Update...'), 'hg-update',
+ self.rupdate)
+
self.singlecmenu = menu
self.singlecmenuitems = items
@@ -1252,6 +1255,10 @@
run.postreview(self.repo.ui, rev=self.repoview.selectedRevisions(),
repo=self.repo)
+ def rupdate(self):
+ run.rupdate(self.repo.ui, rev=self.rev,
+ repo=self.repo)
+
def emailRevision(self):
run.email(self.repo.ui, rev=self.repoview.selectedRevisions(),
repo=self.repo)
diff --git a/tortoisehg/hgqt/run.py b/tortoisehg/hgqt/run.py
--- a/tortoisehg/hgqt/run.py
+++ b/tortoisehg/hgqt/run.py
@@ -565,6 +565,11 @@
from tortoisehg.hgqt.postreview import run
return qtrun(run, ui, *pats, **opts)
+def rupdate(ui, *pats, **opts):
+ """update a remote repository"""
+ from tortoisehg.hgqt.rupdate import run
+ return qtrun(run, ui, *pats, **opts)
+
def merge(ui, *pats, **opts):
"""merge wizard"""
from tortoisehg.hgqt.merge import run
diff --git a/tortoisehg/hgqt/rupdate.py b/tortoisehg/hgqt/rupdate.py
new file mode 100644
--- /dev/null
+++ b/tortoisehg/hgqt/rupdate.py
@@ -0,0 +1,268 @@
+# Copyright 2011 Ryan Seto <mr.we...@gmail.com>
+#
+# rupdate.py - Remote Update dialog for TortoiseHg
+#
+# This dialog lets users update a remote ssh repository.
+#
+# Requires a copy of the rupdate plugin found at:
+# http://bitbucket.org/MrWerewolf/rupdate
+#
+# Also, enable the plugin with the following in mercurial.ini:
+#
+# [extensions]
+# rupdate = /path/to/rupdate
+#
+#
+# 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 error, node, merge as mergemod
+
+from tortoisehg.util import hglib, paths, settings
+from tortoisehg.hgqt.i18n import _
+from tortoisehg.hgqt import cmdui, csinfo, qtlib, thgrepo, resolve
+
+class rUpdateDialog(QDialog):
+
+ output = pyqtSignal(QString, QString)
+ progress = pyqtSignal(QString, object, QString, QString, object)
+ makeLogVisible = pyqtSignal(bool)
+
+ def __init__(self, repo, rev=None, parent=None, opts={}):
+ super(rUpdateDialog, self).__init__(parent)
+ self.setWindowFlags(self.windowFlags() & \
+ ~Qt.WindowContextHelpButtonHint)
+
+ self._finished = False
+ self.repo = repo
+
+ # base layout box
+ box = QVBoxLayout()
+ box.setSpacing(6)
+
+ ## main layout grid
+ grid = QGridLayout()
+ grid.setSpacing(6)
+ box.addLayout(grid)
+
+ # Get configured paths
+ self.paths = {}
+ fn = self.repo.join('hgrc')
+ fn, cfg = qtlib.loadIniFile([fn], self)
+ if 'paths' in cfg:
+ for alias in cfg['paths']:
+ self.paths[ alias ] = cfg['paths'][alias]
+
+ ### target path combo
+
+ self.path_combo = pcombo = QComboBox()
+ pcombo.setEditable(True)
+ grid.addWidget(QLabel(_('Location:')), 0, 0)
+ grid.addWidget(pcombo, 0, 1)
+
+ for alias in self.paths:
+ pcombo.addItem(hglib.tounicode(self.paths[alias]))
+
+ ### target revision combo
+ self.rev_combo = combo = QComboBox()
+ combo.setEditable(True)
+ grid.addWidget(QLabel(_('Update to:')), 1, 0)
+ grid.addWidget(combo, 1, 1)
+
+ if rev is None:
+ rev = self.repo.dirstate.branch()
+ else:
+ rev = str(rev)
+ combo.addItem(hglib.tounicode(rev))
+ combo.setCurrentIndex(0)
+
+ for name in repo.namedbranches:
+ combo.addItem(name)
+
+ tags = list(self.repo.tags()) + repo._bookmarks.keys()
+ tags.sort(reverse=True)
+ for tag in tags:
+ combo.addItem(hglib.tounicode(tag))
+
+ ### target revision info
+ items = ('%(rev)s', ' %(branch)s', ' %(tags)s', '<br />%(summary)s')
+ style = csinfo.labelstyle(contents=items, width=350, selectable=True)
+ factory = csinfo.factory(self.repo, style=style)
+ self.target_info = factory()
+ grid.addWidget(QLabel(_('Target:')), 3, 0, Qt.AlignLeft | Qt.AlignTop)
+ grid.addWidget(self.target_info, 3, 1)
+
+ ### options
+ optbox = QVBoxLayout()
+ optbox.setSpacing(6)
+ expander = qtlib.ExpanderLabel(_('Options:'), False)
+ expander.expanded.connect(self.show_options)
+ row = grid.rowCount()
+ grid.addWidget(expander, row, 0, Qt.AlignLeft | Qt.AlignTop)
+ grid.addLayout(optbox, row, 1)
+
+ self.discard_chk = QCheckBox(_('Discard local changes, no backup'
+ ' (-C/--clean)'))
+ self.push_chk = QCheckBox(_('Perform a push before updating'
+ ' (-p/--push)'))
+ self.newbranch_chk = QCheckBox(_('Allow pushing new branches'
+ ' (--new-branch)'))
+ self.force_chk = QCheckBox(_('Force push to remote location'
+ ' (-f/--force)'))
+ self.showlog_chk = QCheckBox(_('Always show command log'))
+ optbox.addWidget(self.discard_chk)
+ optbox.addWidget(self.push_chk)
+ optbox.addWidget(self.newbranch_chk)
+ optbox.addWidget(self.force_chk)
+ optbox.addWidget(self.showlog_chk)
+
+ self.discard_chk.setChecked(bool(opts.get('clean')))
+
+ ## command widget
+ self.cmd = cmdui.Widget(True, True, self)
+ self.cmd.commandStarted.connect(self.command_started)
+ self.cmd.commandFinished.connect(self.command_finished)
+ self.cmd.commandCanceling.connect(self.command_canceling)
+ self.cmd.output.connect(self.output)
+ self.cmd.makeLogVisible.connect(self.makeLogVisible)
+ self.cmd.progress.connect(self.progress)
+ 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.update_btn = buttons.addButton(_('&Update'),
+ QDialogButtonBox.ActionRole)
+ self.update_btn.clicked.connect(self.update)
+ 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.rev_combo.editTextChanged.connect(lambda *a: self.update_info())
+ self.discard_chk.toggled.connect(lambda *a: self.update_info())
+
+ # dialog setting
+ self.setLayout(box)
+ self.layout().setSizeConstraint(QLayout.SetFixedSize)
+ self.setWindowTitle(_('Update - %s') % self.repo.displayname)
+ self.setWindowIcon(qtlib.geticon('hg-update'))
+
+ # prepare to show
+ self.rev_combo.lineEdit().selectAll()
+ self.cmd.setHidden(True)
+ self.cancel_btn.setHidden(True)
+ self.detail_btn.setHidden(True)
+ self.push_chk.setHidden(True)
+ self.newbranch_chk.setHidden(True)
+ self.force_chk.setHidden(True)
+ self.showlog_chk.setHidden(True)
+ self.update_info()
+
+ ### Private Methods ###
+
+ def update_info(self):
+ new_rev = hglib.fromunicode(self.rev_combo.currentText())
+ if new_rev.lower() == 'null':
+ self.update_btn.setEnabled(True)
+ return
+ try:
+ new_ctx = self.repo[new_rev]
+ self.target_info.update(self.repo[new_rev])
+ self.update_btn.setEnabled(True)
+ except (error.LookupError, error.RepoLookupError, error.RepoError):
+ self.target_info.setText(_('unknown revision!'))
+ self.update_btn.setDisabled(True)
+
+ def update(self):
+ cmdline = ['rupdate']
+
+ if self.discard_chk.isChecked():
+ cmdline.append('--clean')
+ if self.push_chk.isChecked():
+ cmdline.append('--push')
+ if self.newbranch_chk.isChecked():
+ cmdline.append('--new-branch')
+ if self.force_chk.isChecked():
+ cmdline.append('--force')
+
+ dest = hglib.fromunicode(self.path_combo.currentText())
+ cmdline.append('-d')
+ cmdline.append(dest)
+
+ # Refer to the revision by the short hash.
+ rev = hglib.fromunicode(self.rev_combo.currentText())
+ revShortHash = node.short(self.repo[rev].node())
+ cmdline.append(revShortHash)
+
+ # start updating
+ self.repo.incrementBusyCount()
+ self.cmd.run(cmdline)
+
+ ### Signal Handlers ###
+
+ def cancel_clicked(self):
+ self.cmd.cancel()
+ self.reject()
+
+ def detail_toggled(self, checked):
+ self.cmd.setShowOutput(checked)
+
+ def show_options(self, visible):
+ self.push_chk.setShown(visible)
+ self.newbranch_chk.setShown(visible)
+ self.force_chk.setShown(visible)
+ self.showlog_chk.setShown(visible)
+
+ def command_started(self):
+ self.cmd.setShown(True)
+ if self.showlog_chk.isChecked():
+ self.detail_btn.setChecked(True)
+ self.close_btn.setHidden(True)
+ self.cancel_btn.setShown(True)
+ self.detail_btn.setShown(True)
+
+ def command_finished(self, ret):
+ self.repo.decrementBusyCount()
+ if ret not in (0, 1) or self.cmd.outputShown():
+ 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.accept()
+
+ def accept(self):
+ for root, path, status in thgrepo.recursiveMergeStatus(self.repo):
+ if status == 'u':
+ qtlib.InfoMsgBox(_('Merge caused file conflicts'),
+ _('File conflicts need to be resolved'))
+ dlg = resolve.ResolveDialog(self.repo, self)
+ dlg.finished.connect(dlg.deleteLater)
+ dlg.exec_()
+ break
+ super(rUpdateDialog, self).accept()
+
+ def command_canceling(self):
+ self.cancel_btn.setDisabled(True)
+
+def run(ui, *pats, **opts):
+ from tortoisehg.util import paths
+ repo = thgrepo.repository(ui, path=paths.find_root())
+ rev = None
+ if opts.get('rev'):
+ rev = opts.get('rev')
+ elif len(pats) == 1:
+ rev = pats[0]
+ return rUpdateDialog(repo, rev, None, opts)
Interesting
> Remote Update is adds the ability to update a remote repository to a
> specified revision if the remote repository is accessed through ssh.
>
> Note: The default TortoisePlink.exe will give you a "Not enough storage is
> available to process this command" error when you try to run this. To get around
> this, use another version of plink. I use a copy of the TortoisePlink.exe from
> TortoiseCVS.
That is odd. Our TortoisePlink.exe came from TortoiseSVN. Perhaps
TortoiseCVS has updated there's more recently.
I'm mostly ok with this except that it's a near duplication of the
update dialog. Would it be possible to make your dialog class derive
from UpdateDialog and simply override a few methods?
--
Steve Borho
Requires rupdate: http://bitbucket.org/MrWerewolf/rupdate
Remote Update is adds the ability to update a remote repository to a
specified revision if the remote repository is accessed through ssh.
Note: The default TortoisePlink.exe will give you a "Not enough storage is
available to process this command" error when you try to run this. To get around
this, use another version of plink. I use a copy of the TortoisePlink.exe from
TortoiseCVS.
diff --git a/tortoisehg/hgqt/repowidget.py b/tortoisehg/hgqt/repowidget.py
@@ -0,0 +1,132 @@
+# Copyright 2011 Ryan Seto <mr.we...@gmail.com>
+#
+# rupdate.py - Remote Update dialog for TortoiseHg
+#
+# This dialog lets users update a remote ssh repository.
+#
+# Requires a copy of the rupdate plugin found at:
+# http://bitbucket.org/MrWerewolf/rupdate
+#
+# Also, enable the plugin with the following in mercurial.ini:
+#
+# [extensions]
+# rupdate = /path/to/rupdate
+#
+#
+# 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 error, node, merge as mergemod
+
+from tortoisehg.util import hglib, paths
+from tortoisehg.hgqt.i18n import _
+from tortoisehg.hgqt import cmdui, csinfo, qtlib, thgrepo, resolve
+from tortoisehg.hgqt.update import UpdateDialog
+
+class rUpdateDialog(UpdateDialog):
+
+ def __init__(self, repo, rev=None, parent=None, opts={}):
+ super(rUpdateDialog, self).__init__(repo, rev, parent, opts)
+
+ # Get configured paths
+ self.paths = {}
+ fn = self.repo.join('hgrc')
+ fn, cfg = qtlib.loadIniFile([fn], self)
+ if 'paths' in cfg:
+ for alias in cfg['paths']:
+ self.paths[ alias ] = cfg['paths'][alias]
+
+ ### target path combo
+ self.path_combo = pcombo = QComboBox()
+ pcombo.setEditable(True)
+
+ for alias in self.paths:
+ pcombo.addItem(hglib.tounicode(self.paths[alias]))
+
+ ### shift existing items down a row.
+ for i in range(self.grid.count()-1, -1, -1):
+ row, col, rowSp, colSp = self.grid.getItemPosition(i)
+ item = self.grid.takeAt(i)
+ self.grid.removeItem(item)
+ self.grid.addItem(item, row + 1, col, rowSp, colSp, item.alignment())
+
+ ### add target path combo to grid
+ self.grid.addWidget(QLabel(_('Location:')), 0, 0)
+ self.grid.addWidget(pcombo, 0, 1)
+
+ ### Options
+ self.push_chk = QCheckBox(_('Perform a push before updating'
+ ' (-p/--push)'))
+ self.newbranch_chk = QCheckBox(_('Allow pushing new branches'
+ ' (--new-branch)'))
+ self.force_chk = QCheckBox(_('Force push to remote location'
+ ' (-f/--force)'))
+ self.optbox.removeWidget(self.showlog_chk)
+ self.optbox.addWidget(self.push_chk)
+ self.optbox.addWidget(self.newbranch_chk)
+ self.optbox.addWidget(self.force_chk)
+ self.optbox.addWidget(self.showlog_chk)
+
+ # prepare to show
+ self.push_chk.setHidden(True)
+ self.newbranch_chk.setHidden(True)
+ self.force_chk.setHidden(True)
+ self.update_info()
+
+ ### Private Methods ###
+
+ def update_info(self):
+ super(rUpdateDialog, self).update_info()
+
+ # Keep update button enabled.
+ self.update_btn.setDisabled(False)
+ def show_options(self, visible):
+ self.push_chk.setShown(visible)
+ self.newbranch_chk.setShown(visible)
+ self.force_chk.setShown(visible)
+ self.showlog_chk.setShown(visible)
+
+ def command_started(self):
+ super(rUpdateDialog, self).command_started()
+ self.update_btn.setHidden(False)
+
+def run(ui, *pats, **opts):
+ from tortoisehg.util import paths
+ repo = thgrepo.repository(ui, path=paths.find_root())
+ rev = None
+ if opts.get('rev'):
+ rev = opts.get('rev')
+ elif len(pats) == 1:
+ rev = pats[0]
+ return rUpdateDialog(repo, rev, None, opts)
diff --git a/tortoisehg/hgqt/update.py b/tortoisehg/hgqt/update.py
--- a/tortoisehg/hgqt/update.py
+++ b/tortoisehg/hgqt/update.py
@@ -35,15 +35,15 @@
box.setSpacing(6)
## main layout grid
- grid = QGridLayout()
- grid.setSpacing(6)
- box.addLayout(grid)
+ self.grid = QGridLayout()
+ self.grid.setSpacing(6)
+ box.addLayout(self.grid)
### target revision combo
self.rev_combo = combo = QComboBox()
combo.setEditable(True)
- grid.addWidget(QLabel(_('Update to:')), 0, 0)
- grid.addWidget(combo, 0, 1)
+ self.grid.addWidget(QLabel(_('Update to:')), 0, 0)
+ self.grid.addWidget(combo, 0, 1)
if rev is None:
rev = self.repo.dirstate.branch()
@@ -65,31 +65,31 @@
style = csinfo.labelstyle(contents=items, width=350, selectable=True)
factory = csinfo.factory(self.repo, style=style)
self.target_info = factory()
- grid.addWidget(QLabel(_('Target:')), 1, 0, Qt.AlignLeft | Qt.AlignTop)
- grid.addWidget(self.target_info, 1, 1)
+ self.grid.addWidget(QLabel(_('Target:')), 1, 0, Qt.AlignLeft | Qt.AlignTop)
+ self.grid.addWidget(self.target_info, 1, 1)
### parent revision info
self.ctxs = self.repo[None].parents()
if len(self.ctxs) == 2:
self.p1_info = factory()
- grid.addWidget(QLabel(_('Parent 1:')), 2, 0, Qt.AlignLeft | Qt.AlignTop)
- grid.addWidget(self.p1_info, 2, 1)
+ self.grid.addWidget(QLabel(_('Parent 1:')), 2, 0, Qt.AlignLeft | Qt.AlignTop)
+ self.grid.addWidget(self.p1_info, 2, 1)
self.p2_info = factory()
- grid.addWidget(QLabel(_('Parent 2:')), 3, 0, Qt.AlignLeft | Qt.AlignTop)
- grid.addWidget(self.p2_info, 3, 1)
+ self.grid.addWidget(QLabel(_('Parent 2:')), 3, 0, Qt.AlignLeft | Qt.AlignTop)
+ self.grid.addWidget(self.p2_info, 3, 1)
else:
self.p1_info = factory()
- grid.addWidget(QLabel(_('Parent:')), 2, 0, Qt.AlignLeft | Qt.AlignTop)
- grid.addWidget(self.p1_info, 2, 1)
+ self.grid.addWidget(QLabel(_('Parent:')), 2, 0, Qt.AlignLeft | Qt.AlignTop)
+ self.grid.addWidget(self.p1_info, 2, 1)
### options
- optbox = QVBoxLayout()
- optbox.setSpacing(6)
+ self.optbox = QVBoxLayout()
+ self.optbox.setSpacing(6)
expander = qtlib.ExpanderLabel(_('Options:'), False)
expander.expanded.connect(self.show_options)
- row = grid.rowCount()
- grid.addWidget(expander, row, 0, Qt.AlignLeft | Qt.AlignTop)
- grid.addLayout(optbox, row, 1)
+ row = self.grid.rowCount()
+ self.grid.addWidget(expander, row, 0, Qt.AlignLeft | Qt.AlignTop)
+ self.grid.addLayout(self.optbox, row, 1)
self.discard_chk = QCheckBox(_('Discard local changes, no backup '
'(-C/--clean)'))
@@ -97,10 +97,10 @@
self.autoresolve_chk = QCheckBox(_('Automatically resolve merge conflicts '
'where possible'))
self.showlog_chk = QCheckBox(_('Always show command log'))
- optbox.addWidget(self.discard_chk)
- optbox.addWidget(self.merge_chk)
- optbox.addWidget(self.autoresolve_chk)
- optbox.addWidget(self.showlog_chk)
+ self.optbox.addWidget(self.discard_chk)
+ self.optbox.addWidget(self.merge_chk)
+ self.optbox.addWidget(self.autoresolve_chk)
+ self.optbox.addWidget(self.showlog_chk)
self.discard_chk.setChecked(bool(opts.get('clean')))
self.autoresolve_chk.setChecked(
Queued for default, thanks.
--
Steve Borho