Source

thg-qt-mq / graftdialog.patch

Full commit
# HG changeset patch
# User Angel Ezquerra <angel.ezquerra@gmail.com>
# Date 1332113542 -3600
# Branch stable
# Node ID fe66d6b9058baf59cc8370e59e4dfe663517add7
# Parent d6eca4de6c1be79ca5e7b9fb477924555354f8ec
repowidget: add graft support (closes #1377)

Adds a new "graft dialog" that can be shown by using a new "Graft" command that
is shown on the revision context menu, where the transplant command usually is.

The graft dialog is based on the rebase dialog. I have hopefully removed all
unnecessary code from that dialog.

I think it would be best to move all the "history manipulation" commands (such
as graft and phase changes) into the "Modify history" submenu, but that should
be done on another patch.

The graft icon is the same as the transplant icon. However this icon does not
represent graft well. I think a new icon should be made, perhaps one based on
the rebase icon (without the red cross on the source revision).

diff --git a/tortoisehg/hgqt/graft.py b/tortoisehg/hgqt/graft.py
new file mode 100644
--- /dev/null
+++ b/tortoisehg/hgqt/graft.py
@@ -0,0 +1,239 @@
+# graft.py - Graft dialog for TortoiseHg
+#
+# 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.
+
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+
+import os
+
+from mercurial import util, merge as mergemod
+
+from tortoisehg.util import hglib
+from tortoisehg.hgqt.i18n import _
+from tortoisehg.hgqt import qtlib, csinfo, cmdui, resolve, commit, thgrepo
+
+BB = QDialogButtonBox
+
+class GraftDialog(QDialog):
+    showMessage = pyqtSignal(QString)
+
+    def __init__(self, repo, parent, **opts):
+        super(GraftDialog, self).__init__(parent)
+        self.setWindowIcon(qtlib.geticon('hg-graft'))
+        f = self.windowFlags()
+        self.setWindowFlags(f & ~Qt.WindowContextHelpButtonHint)
+        self.repo = repo
+        self.opts = opts
+        self.aborted = False
+
+        box = QVBoxLayout()
+        box.setSpacing(8)
+        box.setContentsMargins(*(6,)*4)
+        self.setLayout(box)
+
+        style = csinfo.panelstyle(selectable=True)
+
+        srcb = QGroupBox( _('Graft changeset'))
+        srcb.setLayout(QVBoxLayout())
+        srcb.layout().setContentsMargins(*(2,)*4)
+        s = opts.get('source', '.')
+        source = csinfo.create(self.repo, s, style, withupdate=True)
+        srcb.layout().addWidget(source)
+        self.layout().addWidget(srcb)
+
+        destb = QGroupBox( _('To graft destination'))
+        destb.setLayout(QVBoxLayout())
+        destb.layout().setContentsMargins(*(2,)*4)
+        d = opts.get('dest', '.')
+        dest = csinfo.create(self.repo, d, style, withupdate=True)
+        destb.layout().addWidget(dest)
+        self.destcsinfo = dest
+        self.layout().addWidget(destb)
+
+        sep = qtlib.LabeledSeparator(_('Options'))
+        self.layout().addWidget(sep)
+
+        self.autoresolvechk = QCheckBox(_('Automatically resolve merge conflicts '
+                                           'where possible'))
+        self.autoresolvechk.setChecked(
+            repo.ui.configbool('tortoisehg', 'autoresolve', False))
+        self.layout().addWidget(self.autoresolvechk)
+
+        self.cmd = cmdui.Widget(True, True, self)
+        self.cmd.commandFinished.connect(self.commandFinished)
+        self.showMessage.connect(self.cmd.stbar.showMessage)
+        self.cmd.stbar.linkActivated.connect(self.linkActivated)
+        self.layout().addWidget(self.cmd, 2)
+
+        bbox = QDialogButtonBox()
+        self.cancelbtn = bbox.addButton(QDialogButtonBox.Cancel)
+        self.cancelbtn.clicked.connect(self.reject)
+        self.graftbtn = bbox.addButton(_('Graft'),
+                                            QDialogButtonBox.ActionRole)
+        self.graftbtn.clicked.connect(self.graft)
+        self.abortbtn = bbox.addButton(_('Abort'),
+                                            QDialogButtonBox.ActionRole)
+        self.abortbtn.clicked.connect(self.abort)
+        self.layout().addWidget(bbox)
+        self.bbox = bbox
+
+        if self.checkResolve() or not (s or d):
+            for w in (srcb, destb, sep, self.keepchk, self.detachchk, 
+                      self.collapsechk, self.keepbrancheschk):
+                w.setHidden(True)
+            self.cmd.setShowOutput(True)
+        else:
+            self.showMessage.emit(_('Checking...'))
+            self.abortbtn.setEnabled(False)
+            self.graftbtn.setEnabled(False)
+            QTimer.singleShot(0, self.checkStatus)
+
+        self.setMinimumWidth(480)
+        self.setMaximumHeight(800)
+        self.resize(0, 340)
+        self.setWindowTitle(_('Graft - %s') % self.repo.displayname)
+
+    def checkStatus(self):
+        repo = self.repo
+        class CheckThread(QThread):
+            def __init__(self, parent):
+                QThread.__init__(self, parent)
+                self.dirty = False
+
+            def run(self):
+                wctx = repo[None]
+                if len(wctx.parents()) > 1:
+                    self.dirty = True
+                elif wctx.dirty():
+                    self.dirty = True
+                else:
+                    for r, p, status in thgrepo.recursiveMergeStatus(repo):
+                        if status == 'u':
+                            self.dirty = True
+                            break
+        def completed():
+            self.th.wait()
+            if self.th.dirty:
+                self.graftbtn.setEnabled(False)
+                txt = _('Before graft, you must <a href="commit">'
+                        '<b>commit</b></a> or <a href="discard">'
+                        '<b>discard</b></a> changes.')
+            else:
+                self.graftbtn.setEnabled(True)
+                txt = _('You may continue the graft')
+            self.showMessage.emit(txt)
+        self.th = CheckThread(self)
+        self.th.finished.connect(completed)
+        self.th.start()
+
+    def graft(self):
+        self.graftbtn.setEnabled(False)
+        self.cancelbtn.setShown(False)
+        cmdline = ['graft', '--repository', self.repo.root]
+        cmdline += ['--config', 'ui.merge=internal:' +
+                    (self.autoresolvechk.isChecked() and 'merge' or 'fail')]
+        if os.path.exists(self.repo.join('graftstate')):
+            cmdline += ['--continue']
+        else:
+            source = self.opts.get('source')
+            dest = self.opts.get('dest')
+            cmdline += [str(source)]
+        self.repo.incrementBusyCount()
+        self.cmd.run(cmdline)
+
+    def abort(self):
+        cmdline = ['graft', '--repository', self.repo.root, '--abort']
+        self.repo.incrementBusyCount()
+        self.aborted = True
+        self.cmd.run(cmdline)
+
+    def commandFinished(self, ret):
+        self.repo.decrementBusyCount()
+        if self.checkResolve() is False:
+            msg = _('Graft is complete')
+            if self.aborted:
+                msg = _('Graft aborted')
+            elif ret == 255:
+                msg = _('Graft failed')
+                self.cmd.setShowOutput(True)  # contains hint
+            self.showMessage.emit(msg)
+            self.graftbtn.setEnabled(True)
+            self.graftbtn.setText(_('Close'))
+            self.graftbtn.clicked.disconnect(self.graft)
+            self.graftbtn.clicked.connect(self.accept)
+
+    def checkResolve(self):
+        for root, path, status in thgrepo.recursiveMergeStatus(self.repo):
+            if status == 'u':
+                txt = _('Graft generated merge <b>conflicts</b> that must '
+                        'be <a href="resolve"><b>resolved</b></a>')
+                self.graftbtn.setEnabled(False)
+                break
+        else:
+            self.graftbtn.setEnabled(True)
+            txt = _('You may continue the graft')
+        self.showMessage.emit(txt)
+
+        if os.path.exists(self.repo.join('graftstate')):
+            self.abortbtn.setEnabled(True)
+            self.graftbtn.setText('Continue')
+            return True
+        else:
+            self.abortbtn.setEnabled(False)
+            return False
+
+    def linkActivated(self, cmd):
+        if cmd == 'resolve':
+            dlg = resolve.ResolveDialog(self.repo, self)
+            dlg.exec_()
+            self.checkResolve()
+        elif cmd == 'commit':
+            dlg = commit.CommitDialog(self.repo, [], {}, self)
+            dlg.finished.connect(dlg.deleteLater)
+            dlg.exec_()
+            self.destcsinfo.update(self.repo['.'])
+            self.checkStatus()
+        elif cmd == 'discard':
+            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):
+                self.repo.decrementBusyCount()
+                if ret == 0:
+                    self.checkStatus()
+            cmdline = ['update', '--clean', '--repository', self.repo.root,
+                       '--rev', '.']
+            self.runner = cmdui.Runner(True, self)
+            self.runner.commandFinished.connect(finished)
+            self.repo.incrementBusyCount()
+            self.runner.run(cmdline)
+
+    def reject(self):
+        if os.path.exists(self.repo.join('graftstate')):
+            main = _('Exiting with an unfinished graft is not recommended.')
+            text = _('Consider aborting the graft first.')
+            labels = ((QMessageBox.Yes, _('&Exit')),
+                      (QMessageBox.No, _('Cancel')))
+            if not qtlib.QuestionMsgBox(_('Confirm Exit'), main, text,
+                                        labels=labels, parent=self):
+                return
+        super(GraftDialog, self).reject()
+
+def run(ui, *pats, **opts):
+    from tortoisehg.util import paths
+    repo = thgrepo.repository(ui, path=paths.find_root())
+    if os.path.exists(repo.join('graftstate')):
+        qtlib.InfoMsgBox(_('Graft already in progress'),
+                          _('Resuming graft already in progress'))
+    elif not opts['source'] or not opts['dest']:
+        qtlib.ErrorMsgBox(_('Abort'),
+                          _('You must provide source and dest arguments'))
+        import sys; sys.exit()
+    return GraftDialog(repo, None, **opts)
diff --git a/tortoisehg/hgqt/repowidget.py b/tortoisehg/hgqt/repowidget.py
--- a/tortoisehg/hgqt/repowidget.py
+++ b/tortoisehg/hgqt/repowidget.py
@@ -25,7 +25,7 @@ from tortoisehg.hgqt import cmdui, updat
 from tortoisehg.hgqt import archive, thgimport, thgstrip, run, purge, bookmark
 from tortoisehg.hgqt import bisect, rebase, resolve, thgrepo, compress, mq
 from tortoisehg.hgqt import qdelete, qreorder, qfold, qrename, shelve
-from tortoisehg.hgqt import matching
+from tortoisehg.hgqt import matching, graft
 
 from tortoisehg.hgqt.repofilter import RepoFilterBar
 from tortoisehg.hgqt.repoview import HgRepoView
@@ -1257,6 +1257,8 @@ class RepoWidget(QWidget):
                       functools.partial(self.changePhase, pnum))
             entry(menu)
 
+        entry(menu, None, fixed, _('Graft to local'), 'hg-transplant',
+              self.graftRevision)
         entry(menu, 'transplant', fixed, _('Transplant to local'), 'hg-transplant',
               self.transplantRevisions)
 
@@ -1867,6 +1869,13 @@ class RepoWidget(QWidget):
         self.runCommand(cmdlines)
         self.reload()
 
+    def graftRevision(self):
+        """Graft selected revision on top of working directory parent"""
+        opts = {'source' : self.rev, 'dest': self.repo['.'].rev()}
+        dlg = graft.GraftDialog(self.repo, self, **opts)
+        dlg.finished.connect(dlg.deleteLater)
+        dlg.exec_()
+
     def rebaseRevision(self):
         """Rebase selected revision on top of working directory parent"""
         opts = {'source' : self.rev, 'dest': self.repo['.'].rev()}