This instance will be upgraded to Heptapod 0.21.0rc2 on 2021-04-16 at 11:00 UTC+2 (a few minutes of down time)

graft.py 10.8 KB
Newer Older
Angel Ezquerra's avatar
Angel Ezquerra committed
1 2 3 4 5 6 7
# 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.

8
from __future__ import absolute_import
Angel Ezquerra's avatar
Angel Ezquerra committed
9 10 11

import os

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
from .qtcore import (
    QSettings,
    QTimer,
    Qt,
    pyqtSlot,
)
from .qtgui import (
    QCheckBox,
    QDialog,
    QDialogButtonBox,
    QGroupBox,
    QMessageBox,
    QVBoxLayout,
)

27 28
from mercurial import scmutil

29 30 31 32 33 34 35 36 37 38 39 40
from ..util import hglib
from ..util.i18n import _
from . import (
    cmdcore,
    cmdui,
    csinfo,
    cslist,
    qtlib,
    resolve,
    thgrepo,
    wctxcleaner,
)
Angel Ezquerra's avatar
Angel Ezquerra committed
41 42 43 44 45

BB = QDialogButtonBox

class GraftDialog(QDialog):

46
    def __init__(self, repoagent, parent, **opts):
Angel Ezquerra's avatar
Angel Ezquerra committed
47 48
        super(GraftDialog, self).__init__(parent)
        self.setWindowIcon(qtlib.geticon('hg-transplant'))
Yuya Nishihara's avatar
Yuya Nishihara committed
49 50
        self.setWindowFlags(self.windowFlags()
                            & ~Qt.WindowContextHelpButtonHint)
51 52

        self._repoagent = repoagent
53
        self._cmdsession = cmdcore.nullCmdSession()
54
        self._graftstatefile = self.repo.vfs.join(b'graftstate')
Angel Ezquerra's avatar
Angel Ezquerra committed
55 56
        self.valid = True

kiilerix's avatar
kiilerix committed
57 58 59
        self.sourcelist = [
            scmutil.revsymbol(self.repo, hglib.fromunicode(rev)).rev()
            for rev in opts.get('source', ['.'])]
Angel Ezquerra's avatar
Angel Ezquerra committed
60 61
        currgraftrevs = self.graftstate()
        if currgraftrevs:
kiilerix's avatar
kiilerix committed
62 63
            currgraftrevs = [scmutil.revsymbol(self.repo, rev).rev()
                             for rev in currgraftrevs]
Angel Ezquerra's avatar
Angel Ezquerra committed
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
            if self.sourcelist != currgraftrevs:
                res = qtlib.CustomPrompt(_('Interrupted graft operation found'),
                    _('An interrupted graft operation has been found.\n\n'
                      'You cannot perform a different graft operation unless '
                      'you abort the interrupted graft operation first.'),
                    self,
                    (_('Continue or abort interrupted graft operation?'),
                     _('Cancel')), 1, 2).run()
                if res != 0:
                    # Cancel
                    self.valid = False
                    return
                # Continue creating the dialog, but use the graft source
                # of the existing, interrupted graft as the source, rather than
                # the one that was passed as an option to the dialog constructor
                self.sourcelist = currgraftrevs

        box = QVBoxLayout()
        box.setSpacing(8)
        box.setContentsMargins(*(6,)*4)
        self.setLayout(box)

        self.srcb = srcb = QGroupBox()
        srcb.setLayout(QVBoxLayout())
        srcb.layout().setContentsMargins(*(2,)*4)

90
        self.cslist = cslist.ChangesetList(self.repo)
Angel Ezquerra's avatar
Angel Ezquerra committed
91
        self._updateSource(0)
92
        srcb.layout().addWidget(self.cslist)
93
        box.addWidget(srcb)
Angel Ezquerra's avatar
Angel Ezquerra committed
94

95
        destrev = self.repo[b'.'].rev()
96
        style = csinfo.panelstyle(selectable=True)
Yuya Nishihara's avatar
Yuya Nishihara committed
97
        destb = QGroupBox(_('To graft destination'))
Angel Ezquerra's avatar
Angel Ezquerra committed
98 99 100 101 102
        destb.setLayout(QVBoxLayout())
        destb.layout().setContentsMargins(*(2,)*4)
        dest = csinfo.create(self.repo, destrev, style, withupdate=True)
        destb.layout().addWidget(dest)
        self.destcsinfo = dest
103
        box.addWidget(destb)
Angel Ezquerra's avatar
Angel Ezquerra committed
104 105

        sep = qtlib.LabeledSeparator(_('Options'))
106
        box.addWidget(sep)
Angel Ezquerra's avatar
Angel Ezquerra committed
107

108 109 110 111 112 113 114 115 116
        self._optchks = {}
        for name, text in [
                ('currentuser', _('Use my user name instead of graft '
                                  'committer user name')),
                ('currentdate', _('Use current date')),
                ('log', _('Append graft info to log message')),
                ('autoresolve', _('Automatically resolve merge conflicts '
                                  'where possible'))]:
            self._optchks[name] = w = QCheckBox(text)
117
            box.addWidget(w)
118

119 120
        self._cmdlog = cmdui.LogWidget(self)
        self._cmdlog.hide()
121
        box.addWidget(self._cmdlog, 2)
122 123
        self._stbar = cmdui.ThgStatusBar(self)
        self._stbar.setSizeGripEnabled(False)
124
        self._stbar.linkActivated.connect(self.linkActivated)
125
        box.addWidget(self._stbar)
Angel Ezquerra's avatar
Angel Ezquerra committed
126 127 128 129

        bbox = QDialogButtonBox()
        self.cancelbtn = bbox.addButton(QDialogButtonBox.Cancel)
        self.cancelbtn.clicked.connect(self.reject)
Yuya Nishihara's avatar
Yuya Nishihara committed
130
        self.graftbtn = bbox.addButton(_('Graft'), QDialogButtonBox.ActionRole)
Angel Ezquerra's avatar
Angel Ezquerra committed
131
        self.graftbtn.clicked.connect(self.graft)
Yuya Nishihara's avatar
Yuya Nishihara committed
132
        self.abortbtn = bbox.addButton(_('Abort'), QDialogButtonBox.ActionRole)
Angel Ezquerra's avatar
Angel Ezquerra committed
133
        self.abortbtn.clicked.connect(self.abort)
134
        box.addWidget(bbox)
Angel Ezquerra's avatar
Angel Ezquerra committed
135 136
        self.bbox = bbox

137 138
        self._wctxcleaner = wctxcleaner.WctxCleaner(repoagent, self)
        self._wctxcleaner.checkFinished.connect(self._onCheckFinished)
Angel Ezquerra's avatar
Angel Ezquerra committed
139 140 141
        if self.checkResolve():
            self.abortbtn.setEnabled(True)
        else:
142
            self._stbar.showMessage(_('Checking...'))
Angel Ezquerra's avatar
Angel Ezquerra committed
143 144
            self.abortbtn.setEnabled(False)
            self.graftbtn.setEnabled(False)
145
            QTimer.singleShot(0, self._wctxcleaner.check)
Angel Ezquerra's avatar
Angel Ezquerra committed
146 147 148 149

        self.setMinimumWidth(480)
        self.setMaximumHeight(800)
        self.resize(0, 340)
150
        self.setWindowTitle(_('Graft - %s') % repoagent.displayName())
151
        self._readSettings()
Angel Ezquerra's avatar
Angel Ezquerra committed
152

153 154 155 156
    @property
    def repo(self):
        return self._repoagent.rawRepo()

157 158 159
    def _readSettings(self):
        qs = QSettings()
        qs.beginGroup('graft')
160
        for n, w in self._optchks.items():
161
            if n == 'autoresolve':
162 163 164
                w.setChecked(
                    self._repoagent.configBool('tortoisehg', n,
                                               qtlib.readBool(qs, n, True)))
165
            else:
166
                w.setChecked(qtlib.readBool(qs, n))
167 168 169 170 171
        qs.endGroup()

    def _writeSettings(self):
        qs = QSettings()
        qs.beginGroup('graft')
172
        for n, w in self._optchks.items():
173
            qs.setValue(n, w.isChecked())
174 175
        qs.endGroup()

Angel Ezquerra's avatar
Angel Ezquerra committed
176 177 178 179 180 181 182 183 184 185
    def _updateSourceTitle(self, idx):
        numrevs = len(self.sourcelist)
        if numrevs <= 1:
            title = _('Graft changeset')
        else:
            title = _('Graft changeset #%d of %d') % (idx + 1, numrevs)
        self.srcb.setTitle(title)

    def _updateSource(self, idx):
        self._updateSourceTitle(idx)
186
        self.cslist.update(self.sourcelist[idx:])
Angel Ezquerra's avatar
Angel Ezquerra committed
187

188 189 190
    @pyqtSlot(bool)
    def _onCheckFinished(self, clean):
        if not clean:
191
            self.graftbtn.setEnabled(False)
192 193 194 195
            txt = _('Before graft, 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.')
196 197 198
        else:
            self.graftbtn.setEnabled(True)
            txt = _('You may continue or start the graft')
199
        self._stbar.showMessage(txt)
200

Angel Ezquerra's avatar
Angel Ezquerra committed
201 202
    def graft(self):
        self.graftbtn.setEnabled(False)
203
        self.cancelbtn.setVisible(False)
204
        opts = dict((n, w.isChecked()) for n, w in self._optchks.items())
205 206
        itool = opts.pop('autoresolve') and 'merge' or 'fail'
        opts['config'] = 'ui.merge=internal:%s' % itool
Angel Ezquerra's avatar
Angel Ezquerra committed
207
        if os.path.exists(self._graftstatefile):
208 209
            opts['continue'] = True
            args = []
Angel Ezquerra's avatar
Angel Ezquerra committed
210
        else:
211 212
            args = [hglib.tounicode(str(s)) for s in self.sourcelist]
        cmdline = hglib.buildcmdargs('graft', *args, **opts)
213 214
        sess = self._runCommand(cmdline)
        sess.commandFinished.connect(self._graftFinished)
Angel Ezquerra's avatar
Angel Ezquerra committed
215 216 217

    def abort(self):
        self.abortbtn.setDisabled(True)
218
        cmdline = hglib.buildcmdargs('graft', abort=True)
219 220
        sess = self._runCommand(cmdline)
        sess.commandFinished.connect(self._abortFinished)
Angel Ezquerra's avatar
Angel Ezquerra committed
221 222

    def graftstate(self):
223
        return hglib.readgraftstate(self.repo)
Angel Ezquerra's avatar
Angel Ezquerra committed
224

225 226 227
    def _runCommand(self, cmdline):
        assert self._cmdsession.isFinished()
        self._cmdsession = sess = self._repoagent.runCommand(cmdline, self)
228
        sess.commandFinished.connect(self._stbar.clearProgress)
229
        sess.outputReceived.connect(self._cmdlog.appendLog)
230
        sess.progressReceived.connect(self._stbar.setProgress)
231
        cmdui.updateStatusMessage(self._stbar, sess)
232
        return sess
233

234 235 236
    @pyqtSlot(int)
    def _graftFinished(self, ret):
        if self.checkResolve() is False:
Angel Ezquerra's avatar
Angel Ezquerra committed
237
            msg = _('Graft is complete')
238
            if ret == 255:
Angel Ezquerra's avatar
Angel Ezquerra committed
239
                msg = _('Graft failed')
240
                self._cmdlog.show()  # contains hint
Angel Ezquerra's avatar
Angel Ezquerra committed
241 242
            else:
                self._updateSource(len(self.sourcelist) - 1)
243
            self._stbar.showMessage(msg)
244 245
            self._makeCloseButton()

246 247 248 249 250 251
    @pyqtSlot()
    def _abortFinished(self):
        if self.checkResolve() is False:
            self._stbar.showMessage(_('Graft aborted'))
            self._makeCloseButton()

252 253 254 255 256
    def _makeCloseButton(self):
        self.graftbtn.setEnabled(True)
        self.graftbtn.setText(_('Close'))
        self.graftbtn.clicked.disconnect(self.graft)
        self.graftbtn.clicked.connect(self.accept)
Angel Ezquerra's avatar
Angel Ezquerra committed
257 258 259

    def checkResolve(self):
        for root, path, status in thgrepo.recursiveMergeStatus(self.repo):
260
            if status == b'u':
Angel Ezquerra's avatar
Angel Ezquerra committed
261 262 263 264 265 266 267
                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')
268
        self._stbar.showMessage(txt)
Angel Ezquerra's avatar
Angel Ezquerra committed
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290

        currgraftrevs = self.graftstate()
        if currgraftrevs:
            def findrev(rev, revlist):
                rev = self.repo[rev].rev()
                for n, r in enumerate(revlist):
                    r = self.repo[r].rev()
                    if rev == r:
                        return n
                return None
            idx = findrev(currgraftrevs[0], self.sourcelist)
            if idx is not None:
                self._updateSource(idx)
            self.abortbtn.setEnabled(True)
            self.graftbtn.setText('Continue')
            return True
        else:
            self.abortbtn.setEnabled(False)
            return False

    def linkActivated(self, cmd):
        if cmd == 'resolve':
291
            dlg = resolve.ResolveDialog(self._repoagent, self)
Angel Ezquerra's avatar
Angel Ezquerra committed
292 293
            dlg.exec_()
            self.checkResolve()
294 295
        else:
            self._wctxcleaner.runCleaner(cmd)
Angel Ezquerra's avatar
Angel Ezquerra committed
296 297

    def reject(self):
298
        if self._wctxcleaner.isChecking():
299
            return
Angel Ezquerra's avatar
Angel Ezquerra committed
300 301 302 303 304 305 306 307 308
        if os.path.exists(self._graftstatefile):
            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()
309 310 311 312

    def done(self, r):
        self._writeSettings()
        super(GraftDialog, self).done(r)