PageRenderTime 32ms CodeModel.GetById 7ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 0ms

/tortoisehg/hgqt/rebase.py

https://bitbucket.org/tortoisehg/hgtk/
Python | 254 lines | 218 code | 30 blank | 6 comment | 37 complexity | 4f01408d6c675e36fcd5a1ac166df84f MD5 | raw file
  1# rebase.py - Rebase dialog for TortoiseHg
  2#
  3# Copyright 2010 Steve Borho <steve@borho.org>
  4#
  5# This software may be used and distributed according to the terms of the
  6# GNU General Public License version 2, incorporated herein by reference.
  7
  8from PyQt4.QtCore import *
  9from PyQt4.QtGui import *
 10
 11import os
 12
 13from mercurial import util, merge as mergemod
 14
 15from tortoisehg.util import hglib
 16from tortoisehg.hgqt.i18n import _
 17from tortoisehg.hgqt import qtlib, csinfo, cmdui, resolve, commit
 18
 19BB = QDialogButtonBox
 20
 21class RebaseDialog(QDialog):
 22    showMessage = pyqtSignal(QString)
 23
 24    def __init__(self, repo, parent, **opts):
 25        super(RebaseDialog, self).__init__(parent)
 26        f = self.windowFlags()
 27        self.setWindowFlags(f & ~Qt.WindowContextHelpButtonHint)
 28        self.repo = repo
 29        self.opts = opts
 30
 31        box = QVBoxLayout()
 32        box.setSpacing(8)
 33        box.setContentsMargins(*(6,)*4)
 34        self.setLayout(box)
 35
 36        style = csinfo.panelstyle(selectable=True)
 37
 38        srcb = QGroupBox( _('Rebase changeset and descendants'))
 39        srcb.setLayout(QVBoxLayout())
 40        srcb.layout().setContentsMargins(*(2,)*4)
 41        s = opts.get('source', '.')
 42        source = csinfo.create(self.repo, s, style, withupdate=True)
 43        srcb.layout().addWidget(source)
 44        self.layout().addWidget(srcb)
 45
 46        destb = QGroupBox( _('To rebase destination'))
 47        destb.setLayout(QVBoxLayout())
 48        destb.layout().setContentsMargins(*(2,)*4)
 49        d = opts.get('dest', '.')
 50        dest = csinfo.create(self.repo, d, style, withupdate=True)
 51        destb.layout().addWidget(dest)
 52        self.destcsinfo = dest
 53        self.layout().addWidget(destb)
 54
 55        sep = qtlib.LabeledSeparator(_('Options'))
 56        self.layout().addWidget(sep)
 57
 58        self.keepchk = QCheckBox(_('Keep original changesets'))
 59        self.keepchk.setChecked(opts.get('keep', False))
 60        self.layout().addWidget(self.keepchk)
 61
 62        self.detachchk = QCheckBox(_('Force detach of rebased changesets '
 63                                     'from their original branch'))
 64        self.detachchk.setChecked(opts.get('detach', True))
 65        self.layout().addWidget(self.detachchk)
 66
 67        self.autoresolvechk = QCheckBox(_('Automatically resolve merge conflicts '
 68                                           'where possible'))
 69        self.autoresolvechk.setChecked(
 70            repo.ui.configbool('tortoisehg', 'autoresolve', False))
 71        self.layout().addWidget(self.autoresolvechk)
 72
 73        if 'hgsubversion' in repo.extensions():
 74            self.svnchk = QCheckBox(_('Rebase unpublished onto Subversion head'
 75                                      ' (override source, destination)'))
 76            self.layout().addWidget(self.svnchk)
 77        else:
 78            self.svnchk = None
 79
 80        self.cmd = cmdui.Widget()
 81        self.cmd.commandFinished.connect(self.commandFinished)
 82        self.showMessage.connect(self.cmd.stbar.showMessage)
 83        self.cmd.stbar.linkActivated.connect(self.linkActivated)
 84        self.layout().addWidget(self.cmd, 2)
 85
 86        bbox = QDialogButtonBox()
 87        self.cancelbtn = bbox.addButton(QDialogButtonBox.Cancel)
 88        self.cancelbtn.clicked.connect(self.reject)
 89        self.rebasebtn = bbox.addButton(_('Rebase'),
 90                                            QDialogButtonBox.ActionRole)
 91        self.rebasebtn.clicked.connect(self.rebase)
 92        self.abortbtn = bbox.addButton(_('Abort'),
 93                                            QDialogButtonBox.ActionRole)
 94        self.abortbtn.clicked.connect(self.abort)
 95        self.layout().addWidget(bbox)
 96        self.bbox = bbox
 97
 98        if self.checkResolve() or not (s or d):
 99            for w in (srcb, destb, sep, self.keepchk, self.detachchk):
100                w.setHidden(True)
101            self.cmd.setShowOutput(True)
102        else:
103            self.showMessage.emit(_('Checking...'))
104            QTimer.singleShot(0, self.checkStatus)
105
106        self.setMinimumWidth(480)
107        self.setMaximumHeight(800)
108        self.resize(0, 340)
109        self.setWindowTitle(_('Rebase - %s') % self.repo.displayname)
110
111    def checkStatus(self):
112        repo = self.repo
113        class CheckThread(QThread):
114            def __init__(self, parent):
115                QThread.__init__(self, parent)
116                self.dirty = False
117
118            def run(self):
119                wctx = repo[None]
120                if len(wctx.parents()) > 1:
121                    self.dirty = True
122                elif wctx.dirty():
123                    self.dirty = True
124                else:
125                    ms = mergemod.mergestate(repo)
126                    unresolved = False
127                    for path in ms:
128                        if ms[path] == 'u':
129                            self.dirty = True
130                            break
131        def completed():
132            self.th.wait()
133            if self.th.dirty:
134                self.rebasebtn.setEnabled(False)
135                txt = _('Before rebase, you must <a href="commit">'
136                        '<b>commit</b></a> or <a href="discard">'
137                        '<b>discard</b></a> changes.')
138            else:
139                self.rebasebtn.setEnabled(True)
140                txt = _('You may continue the rebase')
141            self.showMessage.emit(txt)
142        self.th = CheckThread(self)
143        self.th.finished.connect(completed)
144        self.th.start()
145
146    def rebase(self):
147        self.cancelbtn.setShown(False)
148        self.keepchk.setEnabled(False)
149        self.detachchk.setEnabled(False)
150        cmdline = ['rebase', '--repository', self.repo.root]
151        cmdline += ['--config', 'ui.merge=internal:' +
152                    (self.autoresolvechk.isChecked() and 'merge' or 'fail')]
153        if os.path.exists(self.repo.join('rebasestate')):
154            cmdline += ['--continue']
155        else:
156            if self.keepchk.isChecked():
157                cmdline += ['--keep']
158            if self.detachchk.isChecked():
159                cmdline += ['--detach']
160            if self.svnchk is not None and self.svnchk.isChecked():
161                cmdline += ['--svn']
162            else:
163                source = self.opts.get('source')
164                dest = self.opts.get('dest')
165                cmdline += ['--source', str(source), '--dest', str(dest)]
166        self.repo.incrementBusyCount()
167        self.cmd.run(cmdline)
168
169    def abort(self):
170        cmdline = ['rebase', '--repository', self.repo.root, '--abort']
171        self.repo.incrementBusyCount()
172        self.cmd.run(cmdline)
173
174    def commandFinished(self, ret):
175        self.repo.decrementBusyCount()
176        if self.checkResolve() is False:
177            self.showMessage.emit(_('Rebase is complete'))
178            self.rebasebtn.setText(_('Close'))
179            self.rebasebtn.clicked.disconnect(self.rebase)
180            self.rebasebtn.clicked.connect(self.accept)
181
182    def checkResolve(self):
183        ms = mergemod.mergestate(self.repo)
184        for path in ms:
185            if ms[path] == 'u':
186                txt = _('Rebase generated merge <b>conflicts</b> that must '
187                        'be <a href="resolve"><b>resolved</b></a>')
188                self.rebasebtn.setEnabled(False)
189                break
190        else:
191            self.rebasebtn.setEnabled(True)
192            txt = _('You may continue the rebase')
193        self.showMessage.emit(txt)
194
195        if os.path.exists(self.repo.join('rebasestate')):
196            self.abortbtn.setEnabled(True)
197            self.rebasebtn.setText('Continue')
198            return True
199        else:
200            self.abortbtn.setEnabled(False)
201            return False
202
203    def linkActivated(self, cmd):
204        if cmd == 'resolve':
205            dlg = resolve.ResolveDialog(self.repo, self)
206            dlg.exec_()
207            self.checkResolve()
208        elif cmd == 'commit':
209            dlg = commit.CommitDialog([], dict(root=self.repo.root), self)
210            dlg.finished.connect(dlg.deleteLater)
211            dlg.exec_()
212            self.destcsinfo.update(self.repo['.'])
213            self.checkStatus()
214        elif cmd == 'discard':
215            labels = [(QMessageBox.Yes, _('&Discard')),
216                      (QMessageBox.No, _('Cancel'))]
217            if not qtlib.QuestionMsgBox(_('Confirm Discard'), _('Discard'
218                     ' outstanding changes in working directory?'),
219                     labels=labels, parent=self):
220                return
221            def finished(ret):
222                self.repo.decrementBusyCount()
223                if ret == 0:
224                    self.checkStatus()
225            cmdline = ['update', '--clean', '--repository', self.repo.root,
226                       '--rev', '.']
227            self.runner = cmdui.Runner(_('Discard - TortoiseHg'), True, self)
228            self.runner.commandFinished.connect(finished)
229            self.repo.incrementBusyCount()
230            self.runner.run(cmdline)
231
232    def reject(self):
233        if os.path.exists(self.repo.join('rebasestate')):
234            main = _('Rebase is incomplete, exiting is not recommended')
235            text = _('Abort is recommended before exit.')
236            labels = ((QMessageBox.Yes, _('&Exit')),
237                      (QMessageBox.No, _('Cancel')))
238            if not qtlib.QuestionMsgBox(_('Confirm Exit'), main, text,
239                                        labels=labels, parent=self):
240                return
241        super(RebaseDialog, self).reject()
242
243def run(ui, *pats, **opts):
244    from tortoisehg.util import paths
245    from tortoisehg.hgqt import thgrepo
246    repo = thgrepo.repository(ui, path=paths.find_root())
247    if os.path.exists(repo.join('rebasestate')):
248        qtlib.InfoMsgBox(_('Rebase already in progress'),
249                          _('Resuming rebase already in progress'))
250    elif not opts['source'] or not opts['dest']:
251        qtlib.ErrorMsgBox(_('Abort'),
252                          _('You must provide source and dest arguments'))
253        import sys; sys.exit()
254    return RebaseDialog(repo, None, **opts)