PageRenderTime 90ms CodeModel.GetById 23ms app.highlight 58ms RepoModel.GetById 1ms app.codeStats 1ms

/tortoisehg/hgqt/commit.py

https://bitbucket.org/tortoisehg/hgtk/
Python | 975 lines | 921 code | 32 blank | 22 comment | 70 complexity | 247a839cfe2d8366b5c5190f44b6d906 MD5 | raw file
  1# commit.py - TortoiseHg's commit widget and standalone dialog
  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
  8import os
  9
 10from mercurial import ui, util, error
 11
 12from PyQt4.QtCore import *
 13from PyQt4.QtGui import *
 14from PyQt4.Qsci import QsciScintilla, QsciAPIs, QsciLexerMakefile
 15
 16from tortoisehg.hgqt.i18n import _
 17from tortoisehg.util import hglib, shlib, wconfig
 18
 19from tortoisehg.hgqt import qtlib, qscilib, status, cmdui, branchop, revpanel
 20from tortoisehg.hgqt.sync import loadIniFile
 21
 22# Technical Debt for CommitWidget
 23#  disable commit button while no message is entered or no files are selected
 24#  qtlib decode failure dialog (ask for retry locale, suggest HGENCODING)
 25#  spell check / tab completion
 26#  in-memory patching / committing chunk selected files
 27
 28class MessageEntry(qscilib.Scintilla):
 29
 30    def __init__(self, parent=None):
 31        super(MessageEntry, self).__init__(parent)
 32        self.setEdgeColor(QColor('LightSalmon'))
 33        self.setEdgeMode(QsciScintilla.EdgeLine)
 34        self.setReadOnly(False)
 35        self.setMarginWidth(1, 0)
 36        self.setFont(qtlib.getfont('fontcomment').font())
 37        self.setCaretWidth(10)
 38        self.setCaretLineBackgroundColor(QColor("#e6fff0"))
 39        self.setCaretLineVisible(True)
 40        self.setAutoIndent(True)
 41        self.setAutoCompletionThreshold(2)
 42        self.setAutoCompletionSource(QsciScintilla.AcsAPIs)
 43        self.setAutoCompletionFillupsEnabled(True)
 44        self.setLexer(QsciLexerMakefile(self))
 45        self.lexer().setFont(qtlib.getfont('fontcomment').font())
 46        self.lexer().setColor(QColor(Qt.red), QsciLexerMakefile.Error)
 47        self.setMatchedBraceBackgroundColor(Qt.yellow)
 48        self.setIndentationsUseTabs(False)
 49        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
 50        #self.setIndentationGuidesBackgroundColor(QColor("#e6e6de"))
 51        #self.setFolding(QsciScintilla.BoxedFoldStyle)
 52        # http://www.riverbankcomputing.com/pipermail/qscintilla/2009-February/000461.html
 53        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
 54        self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
 55
 56    def refresh(self, repo):
 57        self.setEdgeColumn(repo.summarylen)
 58        self.setIndentationWidth(repo.tabwidth)
 59        self.setTabWidth(repo.tabwidth)
 60        self.summarylen = repo.summarylen
 61
 62    def reflowBlock(self, line):
 63        lines = self.text().split('\n', QString.KeepEmptyParts)
 64        if line >= len(lines):
 65            return None
 66        if not len(lines[line]) > 1:
 67            return line+1
 68
 69        # find boundaries (empty lines or bounds)
 70        b = line
 71        while b and len(lines[b-1]) > 1:
 72            b = b - 1
 73        e = line
 74        while e+1 < len(lines) and len(lines[e+1]) > 1:
 75            e = e + 1
 76        group = QStringList([lines[l].simplified() for l in xrange(b, e+1)])
 77        sentence = group.join(' ')
 78        parts = sentence.split(' ', QString.SkipEmptyParts)
 79
 80        outlines = QStringList()
 81        line = QStringList()
 82        partslen = 0
 83        for part in parts:
 84            if partslen + len(line) + len(part) + 1 > self.summarylen:
 85                if line:
 86                    outlines.append(line.join(' '))
 87                line, partslen = QStringList(), 0
 88            line.append(part)
 89            partslen += len(part)
 90        if line:
 91            outlines.append(line.join(' '))
 92
 93        self.beginUndoAction()
 94        self.setSelection(b, 0, e+1, 0)
 95        self.removeSelectedText()
 96        self.insertAt(outlines.join('\n')+'\n', b, 0)
 97        self.endUndoAction()
 98        self.setCursorPosition(b, 0)
 99        return b + len(outlines) + 1
100
101    def keyPressEvent(self, event):
102        if event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_E:
103            line, col = self.getCursorPosition()
104            self.reflowBlock(line)
105        super(MessageEntry, self).keyPressEvent(event)
106
107class CommitWidget(QWidget):
108    'A widget that encompasses a StatusWidget and commit extras'
109    commitButtonEnable = pyqtSignal(bool)
110    linkActivated = pyqtSignal(QString)
111    showMessage = pyqtSignal(unicode)
112    commitComplete = pyqtSignal()
113
114    progress = pyqtSignal(QString, object, QString, QString, object)
115    output = pyqtSignal(QString, QString)
116    makeLogVisible = pyqtSignal(bool)
117
118    def __init__(self, pats, opts, root=None, embedded=False, parent=None):
119        QWidget.__init__(self, parent=parent)
120
121        self.opts = opts # user, date
122        self.stwidget = status.StatusWidget(pats, opts, root, self)
123        self.stwidget.showMessage.connect(self.showMessage)
124        self.stwidget.progress.connect(self.progress)
125        self.stwidget.linkActivated.connect(self.linkActivated)
126        self.stwidget.fileDisplayed.connect(self.fileDisplayed)
127        self.msghistory = []
128        self.repo = repo = self.stwidget.repo
129        self.runner = cmdui.Runner(_('Commit'), not embedded, self)
130        self.runner.output.connect(self.output)
131        self.runner.progress.connect(self.progress)
132        self.runner.makeLogVisible.connect(self.makeLogVisible)
133        self.runner.commandFinished.connect(self.commandFinished)
134
135        repo.configChanged.connect(self.configChanged)
136        repo.repositoryChanged.connect(self.repositoryChanged)
137        repo.workingBranchChanged.connect(self.workingBranchChanged)
138
139        self.opts['pushafter'] = repo.ui.config('tortoisehg', 'cipushafter', '')
140        self.opts['autoinc'] = repo.ui.config('tortoisehg', 'autoinc', '')
141
142        layout = QVBoxLayout()
143        layout.setContentsMargins(2, 2, 2, 2)
144        layout.setSpacing(0)
145        layout.addWidget(self.stwidget)
146        self.setLayout(layout)
147
148        vbox = QVBoxLayout()
149        vbox.setMargin(0)
150        vbox.setSpacing(0)
151        vbox.setContentsMargins(*(0,)*4)
152
153        hbox = QHBoxLayout()
154        hbox.setMargin(0)
155        hbox.setContentsMargins(*(0,)*4)
156
157        msgcombo = MessageHistoryCombo()
158        msgcombo.activated.connect(self.msgSelected)
159        hbox.addWidget(msgcombo, 1)
160
161        branchbutton = QPushButton(_('Branch: '))
162        branchbutton.pressed.connect(self.branchOp)
163        self.branchbutton = branchbutton
164        self.branchop = None
165        hbox.addWidget(branchbutton)
166
167        self.detailsbutton = QPushButton(_('Options'))
168        self.detailsbutton.pressed.connect(self.details)
169        hbox.addWidget(self.detailsbutton)
170
171        vbox.addLayout(hbox, 0)
172        self.buttonHBox = hbox
173
174        self.pcsinfo = revpanel.ParentWidget(repo)
175        vbox.addWidget(self.pcsinfo, 0)
176
177        msgte = MessageEntry(self)
178        msgte.setContextMenuPolicy(Qt.CustomContextMenu)
179        msgte.customContextMenuRequested.connect(self.menuRequested)
180        msgte.installEventFilter(qscilib.KeyPressInterceptor(self))
181        vbox.addWidget(msgte, 1)
182        upperframe = QFrame()
183
184        SP = QSizePolicy
185        sp = SP(SP.Expanding, SP.Expanding)
186        sp.setHorizontalStretch(1)
187        upperframe.setSizePolicy(sp)
188        upperframe.setLayout(vbox)
189
190        self.split = QSplitter(Qt.Vertical)
191        sp = SP(SP.Expanding, SP.Expanding)
192        sp.setHorizontalStretch(1)
193        sp.setVerticalStretch(0)
194        self.split.setSizePolicy(sp)
195        # Add our widgets to the top of our splitter
196        self.split.addWidget(upperframe)
197        # Add status widget document frame below our splitter
198        # this reparents the docf from the status splitter
199        self.split.addWidget(self.stwidget.docf)
200
201        # add our splitter where the docf used to be
202        self.stwidget.split.addWidget(self.split)
203        self.msgte = msgte
204        self.msgcombo = msgcombo
205
206    @pyqtSlot(QString, QString)
207    def fileDisplayed(self, wfile, contents):
208        'Status widget is displaying a new file'
209        if not (wfile and contents):
210            return
211        wfile = unicode(wfile)
212        self._apis = QsciAPIs(self.msgte.lexer())
213        tokens = set()
214        for e in self.stwidget.getChecked():
215            tokens.add(e)
216            tokens.add(os.path.basename(e))
217        try:
218            from pygments.lexers import guess_lexer_for_filename
219            from pygments.token import Token
220            from pygments.util import ClassNotFound
221            try:
222                contents = unicode(contents)
223                lexer = guess_lexer_for_filename(wfile, contents)
224                for tokentype, value in lexer.get_tokens(contents):
225                    if tokentype in Token.Name and len(value) > 4:
226                        tokens.add(value)
227            except ClassNotFound:
228                pass
229        except ImportError:
230            pass
231        for n in sorted(list(tokens)):
232            self._apis.add(n)
233        self._apis.apiPreparationFinished.connect(self.apiPrepFinished)
234        self._apis.prepare()
235
236    def apiPrepFinished(self):
237        'QsciAPIs has finished parsing displayed file'
238        self.msgte.lexer().setAPIs(self._apis)
239
240    def details(self):
241        dlg = DetailsDialog(self.opts, self.userhist, self)
242        dlg.finished.connect(dlg.deleteLater)
243        dlg.setWindowFlags(Qt.Sheet)
244        dlg.setWindowModality(Qt.WindowModal)
245        if dlg.exec_() == QDialog.Accepted:
246            self.opts.update(dlg.outopts)
247            self.refresh()
248
249    def workingBranchChanged(self):
250        'Repository has detected a change in .hg/branch'
251        self.refresh()
252
253    def repositoryChanged(self):
254        'Repository has detected a changelog / dirstate change'
255        self.refresh()
256
257    def configChanged(self):
258        'Repository is reporting its config files have changed'
259        self.refresh()
260
261    @pyqtSlot()
262    def reload(self):
263        'User has requested a reload'
264        self.repo.thginvalidate()
265        self.refresh()
266        self.stwidget.refreshWctx() # Trigger reload of working context
267
268    def refresh(self):
269        ispatch = self.repo.changectx('.').thgmqappliedpatch()
270        self.commitButtonEnable.emit(not ispatch)
271        self.msgte.refresh(self.repo)
272
273        # Update message list
274        self.msgcombo.reset(self.msghistory)
275
276        # Update branch operation button
277        branchu = hglib.tounicode(self.repo[None].branch())
278        if self.branchop is None:
279            title = _('Branch: ') + branchu
280        elif self.branchop == False:
281            title = _('Close Branch: ') + branchu
282        else:
283            title = _('New Branch: ') + self.branchop
284        self.branchbutton.setText(title)
285
286        # Update parent csinfo widget
287        self.pcsinfo.set_revision(None)
288        self.pcsinfo.update()
289
290    def menuRequested(self, point):
291        line = self.msgte.lineAt(point)
292        point = self.msgte.mapToGlobal(point)
293
294        def apply():
295            line = 0
296            while True:
297                line = self.msgte.reflowBlock(line)
298                if line is None:
299                    break;
300        def paste():
301            files = self.stwidget.getChecked()
302            self.msgte.insert(', '.join(files))
303        def settings():
304            from tortoisehg.hgqt.settings import SettingsDialog
305            dlg = SettingsDialog(True, focus='tortoisehg.summarylen')
306            dlg.exec_()
307
308        menu = self.msgte.createStandardContextMenu()
309        menu.addSeparator()
310        for name, func in [(_('Paste &Filenames'), paste),
311                           (_('App&ly Format'), apply),
312                           (_('C&onfigure Format'), settings)]:
313            def add(name, func):
314                action = menu.addAction(name)
315                action.triggered.connect(lambda: func())
316            add(name, func)
317        return menu.exec_(point)
318
319    def branchOp(self):
320        d = branchop.BranchOpDialog(self.repo, self.branchop, self)
321        d.setWindowFlags(Qt.Sheet)
322        d.setWindowModality(Qt.WindowModal)
323        if d.exec_() == QDialog.Accepted:
324            self.branchop = d.branchop
325            self.refresh()
326
327    def canUndo(self):
328        'Returns undo description or None if not valid'
329        if os.path.exists(self.repo.sjoin('undo')):
330            try:
331                args = self.repo.opener('undo.desc', 'r').read().splitlines()
332                if args[1] != 'commit':
333                    return None
334                return _('Rollback commit to revision %d') % (int(args[0]) - 1)
335            except (IOError, IndexError, ValueError):
336                pass
337        return None
338
339    def rollback(self):
340        msg = self.canUndo()
341        if not msg:
342            return
343        d = QMessageBox.question(self, _('Confirm Undo'), msg,
344                                 QMessageBox.Ok | QMessageBox.Cancel)
345        if d != QMessageBox.Ok:
346            return
347        self.repo.incrementBusyCount()
348        self.repo.rollback()
349        self.repo.decrementBusyCount()
350        self.reload()
351        QTimer.singleShot(500, lambda: shlib.shell_notify([self.repo.root]))
352
353    def getMessage(self):
354        text = self.msgte.text()
355        try:
356            text = hglib.fromunicode(text, 'strict')
357        except UnicodeEncodeError:
358            pass # TODO
359        return text
360
361    def msgSelected(self, index):
362        if self.msgte.text() and self.msgte.isModified():
363            d = QMessageBox.question(self, _('Confirm Discard Message'),
364                        _('Discard current commit message?'),
365                        QMessageBox.Ok | QMessageBox.Cancel)
366            if d != QMessageBox.Ok:
367                return
368        self.setMessage(self.msghistory[index])
369        self.msgte.setFocus()
370
371    def setMessage(self, msg):
372        self.msgte.setText(msg)
373        lines = self.msgte.lines()
374        if lines:
375            lines -= 1
376            pos = self.msgte.lineLength(lines)
377            self.msgte.setCursorPosition(lines, pos)
378            self.msgte.ensureLineVisible(lines)
379            hs = self.msgte.horizontalScrollBar()
380            hs.setSliderPosition(0)
381        self.msgte.setModified(False)
382
383    def canExit(self):
384        # Usually safe to exit, since we're saving messages implicitly
385        # We'll ask the user for confirmation later, if they have any
386        # files partially selected.
387        return not self.runner.core.running()
388
389    def loadSettings(self, s, prefix):
390        'Load history, etc, from QSettings instance'
391        repoid = str(self.repo[0])
392        lpref = prefix + '/commit/' # local settings (splitter, etc)
393        gpref = 'commit/'           # global settings (history, etc)
394        # message history is stored in unicode
395        self.split.restoreState(s.value(lpref+'split').toByteArray())
396        self.msgte.loadSettings(s, lpref+'msgte')
397        self.stwidget.loadSettings(s, lpref+'status')
398        self.msghistory = list(s.value(gpref+'history-'+repoid).toStringList())
399        self.msghistory = [m for m in self.msghistory if m]
400        self.msgcombo.reset(self.msghistory)
401        self.userhist = s.value(gpref+'userhist').toStringList()
402        self.userhist = [u for u in self.userhist if u]
403        try:
404            curmsg = self.repo.opener('cur-message.txt').read()
405            self.setMessage(hglib.tounicode(curmsg))
406        except EnvironmentError:
407            pass
408        try:
409            curmsg = self.repo.opener('last-message.txt').read()
410            if curmsg:
411                self.addMessageToHistory(hglib.tounicode(curmsg))
412        except EnvironmentError:
413            pass
414
415    def saveSettings(self, s, prefix):
416        'Save history, etc, in QSettings instance'
417        repoid = str(self.repo[0])
418        lpref = prefix + '/commit/'
419        gpref = 'commit/'
420        s.setValue(lpref+'split', self.split.saveState())
421        self.msgte.saveSettings(s, lpref+'msgte')
422        self.stwidget.saveSettings(s, lpref+'status')
423        s.setValue(gpref+'history-'+repoid, self.msghistory)
424        s.setValue(gpref+'userhist', self.userhist)
425        try:
426            msg = self.getMessage()
427            self.repo.opener('cur-message.txt', 'w').write(msg)
428        except EnvironmentError:
429            pass
430
431    def addMessageToHistory(self, umsg):
432        if umsg in self.msghistory:
433            self.msghistory.remove(umsg)
434        self.msghistory.insert(0, umsg)
435        self.msghistory = self.msghistory[:10]
436
437    def addUsernameToHistory(self, user):
438        if user in self.userhist:
439            self.userhist.remove(user)
440        self.userhist.insert(0, user)
441        self.userhist = self.userhist[:10]
442
443    def getCurrentUsername(self):
444        # 1. Override has highest priority
445        user = self.opts.get('user')
446        if user:
447            return user
448
449        # 2. Read from repository
450        try:
451            return self.repo.ui.username()
452        except error.Abort:
453            pass
454
455        # 3. Get a username from the user
456        QMessageBox.information(self, _('Please enter a username'),
457                    _('You must identify yourself to Mercurial'),
458                    QMessageBox.Ok)
459        from tortoisehg.hgqt.settings import SettingsDialog
460        dlg = SettingsDialog(False, focus='ui.username')
461        dlg.exec_()
462        self.repo.invalidateui()
463        try:
464            return self.repo.ui.username()
465        except error.Abort:
466            return None
467
468    def commit(self):
469        repo = self.repo
470        msg = self.getMessage()
471        if not msg:
472            qtlib.WarningMsgBox(_('Nothing Commited'),
473                                _('Please enter commit message'),
474                                parent=self)
475            self.msgte.setFocus()
476            return
477
478        commandlines = []
479
480        brcmd = []
481        newbranch = False
482        if self.branchop is None:
483            newbranch = repo[None].branch() != repo['.'].branch()
484        elif self.branchop == False:
485            brcmd = ['--close-branch']
486        else:
487            branch = hglib.fromunicode(self.branchop)
488            if branch in repo.branchtags():
489                # response: 0=Yes, 1=No, 2=Cancel
490                if branch in [p.branch() for p in repo.parents()]:
491                    resp = 0
492                else:
493                    rev = repo[branch].rev()
494                    resp = qtlib.CustomPrompt(_('Confirm Branch Change'),
495                        _('Named branch "%s" already exists, '
496                          'last used in revision %d\n'
497                          'Yes\t- Make commit restarting this named branch\n'
498                          'No\t- Make commit without changing branch\n'
499                          'Cancel\t- Cancel this commit') % (self.branchop, rev),
500                          self, (_('&Yes'), _('&No'), _('Cancel')), 2, 2).run()
501            else:
502                resp = qtlib.CustomPrompt(_('Confirm New Branch'),
503                    _('Create new named branch "%s" with this commit?\n'
504                      'Yes\t- Start new branch with this commit\n'
505                      'No\t- Make commit without branch change\n'
506                      'Cancel\t- Cancel this commit') % self.branchop,
507                    self, (_('&Yes'), _('&No'), _('Cancel')), 2, 2).run()
508            if resp == 0:
509                newbranch = True
510                commandlines.append(['branch', '--repository', repo.root,
511                                     '--force', branch])
512            elif resp == 2:
513                return
514        files = self.stwidget.getChecked('MAR?!S')
515        if not (files or brcmd or newbranch):
516            qtlib.WarningMsgBox(_('No files checked'),
517                                _('No modified files checkmarked for commit'),
518                                parent=self)
519            self.stwidget.tv.setFocus()
520            return
521        if len(repo.parents()) > 1:
522            files = []
523
524        user = self.getCurrentUsername()
525        if not user:
526            return
527        self.addUsernameToHistory(user)
528
529        checkedUnknowns = self.stwidget.getChecked('?I')
530        if checkedUnknowns:
531            res = qtlib.CustomPrompt(
532                    _('Confirm Add'),
533                    _('Add checked untracked files?'), self,
534                    (_('&OK'), _('Cancel')), 0, 1,
535                    checkedUnknowns).run()
536            if res == 0:
537                cmd = ['add', '--repository', repo.root] + \
538                      [repo.wjoin(f) for f in checkedUnknowns]
539                commandlines.append(cmd)
540            else:
541                return
542        checkedMissing = self.stwidget.getChecked('!')
543        if checkedMissing:
544            res = qtlib.CustomPrompt(
545                    _('Confirm Remove'),
546                    _('Remove checked deleted files?'), self,
547                    (_('&OK'), _('Cancel')), 0, 1,
548                    checkedMissing).run()
549            if res == 0:
550                cmd = ['remove', '--repository', repo.root] + \
551                      [repo.wjoin(f) for f in checkedMissing]
552                commandlines.append(cmd)
553            else:
554                return
555        try:
556            date = self.opts.get('date')
557            if date:
558                util.parsedate(date)
559                dcmd = ['--date', date]
560            else:
561                dcmd = []
562        except error.Abort, e:
563            if e.hint:
564                err = _('%s (hint: %s)') % (hglib.tounicode(str(e)),
565                                            hglib.tounicode(e.hint))
566            else:
567                err = hglib.tounicode(str(e))
568            self.showMessage.emit(err)
569            dcmd = []
570        cmdline = ['commit', '--repository', repo.root, '--verbose',
571                   '--user', user, '--message='+msg]
572        cmdline += dcmd + brcmd + [repo.wjoin(f) for f in files]
573        for fname in self.opts.get('autoinc', '').split(','):
574            fname = fname.strip()
575            if fname:
576                cmdline.extend(['--include', fname])
577
578        commandlines.append(cmdline)
579
580        if self.opts.get('pushafter'):
581            cmd = ['push', '--repository', repo.root, self.opts['pushafter']]
582            commandlines.append(cmd)
583
584        repo.incrementBusyCount()
585        self.runner.run(*commandlines)
586
587    def commandFinished(self, ret):
588        self.repo.decrementBusyCount()
589        if ret == 0:
590            umsg = self.msgte.text()
591            if umsg:
592                self.addMessageToHistory(umsg)
593            self.msgte.clear()
594            self.msgte.setModified(False)
595            self.commitComplete.emit()
596        self.stwidget.refreshWctx()
597
598    def keyPressEvent(self, event):
599        if event.key() in (Qt.Key_Return, Qt.Key_Enter):
600            if event.modifiers() == Qt.ControlModifier:
601                self.commit()
602            return
603        return super(CommitWidget, self).keyPressEvent(event)
604
605class MessageHistoryCombo(QComboBox):
606    def __init__(self, parent=None):
607        QComboBox.__init__(self, parent)
608        self.reset([])
609
610    def reset(self, msgs):
611        self.clear()
612        self.addItem(_('Recent commit messages...'))
613        self.loaded = False
614        self.msgs = msgs
615
616    def showPopup(self):
617        if not self.loaded:
618            self.clear()
619            for s in self.msgs:
620                self.addItem(s.split('\n', 1)[0][:70])
621            self.loaded = True
622        QComboBox.showPopup(self)
623
624
625class DetailsDialog(QDialog):
626    'Utility dialog for configuring uncommon settings'
627    def __init__(self, opts, userhistory, parent):
628        QDialog.__init__(self, parent)
629        self.setWindowTitle('%s - commit options' % parent.repo.displayname)
630        self.repo = parent.repo
631
632        layout = QVBoxLayout()
633        self.setLayout(layout)
634
635        hbox = QHBoxLayout()
636        self.usercb = QCheckBox(_('Set username:'))
637
638        usercombo = QComboBox()
639        usercombo.setEditable(True)
640        usercombo.setEnabled(False)
641        self.usercb.toggled.connect(usercombo.setEnabled)
642        self.usercb.toggled.connect(lambda s: s and usercombo.setFocus())
643
644        l = []
645        if opts.get('user'):
646            val = hglib.tounicode(opts['user'])
647            self.usercb.setChecked(True)
648            l.append(val)
649        try:
650            val = hglib.tounicode(self.repo.ui.username())
651            l.append(val)
652        except util.Abort:
653            pass
654        for name in userhistory:
655            if name not in l:
656                l.append(name)
657        for name in l:
658            usercombo.addItem(name)
659        self.usercombo = usercombo
660
661        usersaverepo = QPushButton(_('Save in Repo'))
662        usersaverepo.clicked.connect(self.saveInRepo)
663        usersaverepo.setEnabled(False)
664        self.usercb.toggled.connect(usersaverepo.setEnabled)
665
666        usersaveglobal = QPushButton(_('Save Global'))
667        usersaveglobal.clicked.connect(self.saveGlobal)
668        usersaveglobal.setEnabled(False)
669        self.usercb.toggled.connect(usersaveglobal.setEnabled)
670
671        hbox.addWidget(self.usercb)
672        hbox.addWidget(self.usercombo)
673        hbox.addWidget(usersaverepo)
674        hbox.addWidget(usersaveglobal)
675        layout.addLayout(hbox)
676
677        hbox = QHBoxLayout()
678        self.datecb = QCheckBox(_('Set Date:'))
679        self.datele = QLineEdit()
680        self.datele.setEnabled(False)
681        self.datecb.toggled.connect(self.datele.setEnabled)
682        curdate = QPushButton(_('Update'))
683        curdate.setEnabled(False)
684        self.datecb.toggled.connect(curdate.setEnabled)
685        self.datecb.toggled.connect(lambda s: s and curdate.setFocus())
686        curdate.clicked.connect( lambda: self.datele.setText(
687                hglib.tounicode(hglib.displaytime(util.makedate()))))
688        if opts.get('date'):
689            self.datele.setText(opts['date'])
690            self.datecb.setChecked(True)
691        else:
692            self.datecb.setChecked(False)
693            curdate.clicked.emit(True)
694
695        hbox.addWidget(self.datecb)
696        hbox.addWidget(self.datele)
697        hbox.addWidget(curdate)
698        layout.addLayout(hbox)
699
700        hbox = QHBoxLayout()
701        self.pushaftercb = QCheckBox(_('Push After Commit:'))
702        self.pushafterle = QLineEdit()
703        self.pushafterle.setEnabled(False)
704        self.pushaftercb.toggled.connect(self.pushafterle.setEnabled)
705        self.pushaftercb.toggled.connect(lambda s:
706                s and self.pushafterle.setFocus())
707
708        pushaftersave = QPushButton(_('Save in Repo'))
709        pushaftersave.clicked.connect(self.savePushAfter)
710        pushaftersave.setEnabled(False)
711        self.pushaftercb.toggled.connect(pushaftersave.setEnabled)
712
713        if opts.get('pushafter'):
714            val = hglib.tounicode(opts['pushafter'])
715            self.pushafterle.setText(val)
716            self.pushaftercb.setChecked(True)
717
718        hbox.addWidget(self.pushaftercb)
719        hbox.addWidget(self.pushafterle)
720        hbox.addWidget(pushaftersave)
721        layout.addLayout(hbox)
722
723        hbox = QHBoxLayout()
724        self.autoinccb = QCheckBox(_('Auto Includes:'))
725        self.autoincle = QLineEdit()
726        self.autoincle.setEnabled(False)
727        self.autoinccb.toggled.connect(self.autoincle.setEnabled)
728        self.autoinccb.toggled.connect(lambda s:
729                s and self.autoincle.setFocus())
730
731        autoincsave = QPushButton(_('Save in Repo'))
732        autoincsave.clicked.connect(self.saveAutoInc)
733        autoincsave.setEnabled(False)
734        self.autoinccb.toggled.connect(autoincsave.setEnabled)
735
736        if opts.get('autoinc'):
737            val = hglib.tounicode(opts['autoinc'])
738            self.autoincle.setText(val)
739            self.autoinccb.setChecked(True)
740
741        hbox.addWidget(self.autoinccb)
742        hbox.addWidget(self.autoincle)
743        hbox.addWidget(autoincsave)
744        layout.addLayout(hbox)
745
746        BB = QDialogButtonBox
747        bb = QDialogButtonBox(BB.Ok|BB.Cancel)
748        bb.accepted.connect(self.accept)
749        bb.rejected.connect(self.reject)
750        self.bb = bb
751        layout.addWidget(bb)
752
753    def saveInRepo(self):
754        fn = os.path.join(self.repo.root, '.hg', 'hgrc')
755        self.saveToPath([fn])
756
757    def saveGlobal(self):
758        self.saveToPath(util.user_rcpath())
759
760    def saveToPath(self, path):
761        fn, cfg = loadIniFile(path, self)
762        if not hasattr(cfg, 'write'):
763            qtlib.WarningMsgBox(_('Unable to save username'),
764                   _('Iniparse must be installed.'), parent=self)
765            return
766        if fn is None:
767            return
768        try:
769            user = hglib.fromunicode(self.usercombo.currentText())
770            if user:
771                cfg.set('ui', 'username', user)
772            else:
773                try:
774                    del cfg['ui']['username']
775                except KeyError:
776                    pass
777            wconfig.writefile(cfg, fn)
778        except IOError, e:
779            qtlib.WarningMsgBox(_('Unable to write configuration file'),
780                                hglib.tounicode(e), parent=self)
781
782    def savePushAfter(self):
783        path = os.path.join(self.repo.root, '.hg', 'hgrc')
784        fn, cfg = loadIniFile([path], self)
785        if not hasattr(cfg, 'write'):
786            qtlib.WarningMsgBox(_('Unable to save after commit push'),
787                   _('Iniparse must be installed.'), parent=self)
788            return
789        if fn is None:
790            return
791        try:
792            remote = hglib.fromunicode(self.pushafterle.text())
793            if remote:
794                cfg.set('tortoisehg', 'cipushafter', remote)
795            else:
796                try:
797                    del cfg['tortoisehg']['cipushafter']
798                except KeyError:
799                    pass
800            wconfig.writefile(cfg, fn)
801        except IOError, e:
802            qtlib.WarningMsgBox(_('Unable to write configuration file'),
803                                hglib.tounicode(e), parent=self)
804
805    def saveAutoInc(self):
806        path = os.path.join(self.repo.root, '.hg', 'hgrc')
807        fn, cfg = loadIniFile([path], self)
808        if not hasattr(cfg, 'write'):
809            qtlib.WarningMsgBox(_('Unable to save auto include list'),
810                   _('Iniparse must be installed.'), parent=self)
811            return
812        if fn is None:
813            return
814        try:
815            list = hglib.fromunicode(self.autoincle.text())
816            if list:
817                cfg.set('tortoisehg', 'autoinc', list)
818            else:
819                try:
820                    del cfg['tortoisehg']['autoinc']
821                except KeyError:
822                    pass
823            wconfig.writefile(cfg, fn)
824        except IOError, e:
825            qtlib.WarningMsgBox(_('Unable to write configuration file'),
826                                hglib.tounicode(e), parent=self)
827
828    def accept(self):
829        outopts = {}
830        if self.datecb.isChecked():
831            date = hglib.fromunicode(self.datele.text())
832            try:
833                util.parsedate(date)
834            except error.Abort, e:
835                if e.hint:
836                    err = _('%s (hint: %s)') % (hglib.tounicode(str(e)),
837                                                hglib.tounicode(e.hint))
838                else:
839                    err = hglib.tounicode(str(e))
840                qtlib.WarningMsgBox(_('Invalid date format'), err, parent=self)
841                return
842            outopts['date'] = date
843        else:
844            outopts['date'] = ''
845
846        if self.usercb.isChecked():
847            user = hglib.fromunicode(self.usercombo.currentText())
848        else:
849            user = ''
850        outopts['user'] = user
851        if not user:
852            try:
853                self.repo.ui.username()
854            except util.Abort, e:
855                if e.hint:
856                    err = _('%s (hint: %s)') % (hglib.tounicode(str(e)),
857                                                hglib.tounicode(e.hint))
858                else:
859                    err = hglib.tounicode(str(e))
860                qtlib.WarningMsgBox(_('No username configured'),
861                                    err, parent=self)
862                return
863
864        if self.pushaftercb.isChecked():
865            remote = hglib.fromunicode(self.pushafterle.text())
866            outopts['pushafter'] = remote
867        else:
868            outopts['pushafter'] = ''
869
870        self.outopts = outopts
871        QDialog.accept(self)
872
873
874class CommitDialog(QDialog):
875    'Standalone commit tool, a wrapper for CommitWidget'
876
877    def __init__(self, pats, opts, parent=None):
878        QDialog.__init__(self, parent)
879        self.setWindowFlags(Qt.Window)
880        self.pats = pats
881        self.opts = opts
882
883        layout = QVBoxLayout()
884        layout.setContentsMargins(2, 2, 2, 2)
885        layout.setMargin(0)
886        self.setLayout(layout)
887
888        commit = CommitWidget(pats, opts, opts.get('root'), False, self)
889        layout.addWidget(commit, 1)
890
891        self.statusbar = cmdui.ThgStatusBar(self)
892        self.statusbar.setSizeGripEnabled(False)
893        commit.showMessage.connect(self.statusbar.showMessage)
894        commit.progress.connect(self.statusbar.progress)
895        commit.linkActivated.connect(self.linkActivated)
896
897        BB = QDialogButtonBox
898        bb = QDialogButtonBox(BB.Ok|BB.Cancel|BB.Discard)
899        bb.accepted.connect(self.accept)
900        bb.rejected.connect(self.reject)
901        bb.button(BB.Discard).setText('Undo')
902        bb.button(BB.Discard).clicked.connect(commit.rollback)
903        bb.button(BB.Cancel).setDefault(False)
904        bb.button(BB.Discard).setDefault(False)
905        bb.button(BB.Ok).setDefault(True)
906        self.commitButton = bb.button(BB.Ok)
907        self.commitButton.setText(_('Commit'))
908        self.bb = bb
909
910        hbox = QHBoxLayout()
911        hbox.setMargin(0)
912        hbox.setContentsMargins(*(0,)*4)
913        hbox.addWidget(self.statusbar)
914        hbox.addWidget(self.bb)
915        layout.addLayout(hbox)
916
917        s = QSettings()
918        self.restoreGeometry(s.value('commit/geom').toByteArray())
919        commit.loadSettings(s, 'committool')
920        commit.repo.repositoryChanged.connect(self.updateUndo)
921        commit.commitComplete.connect(self.postcommit)
922        commit.commitButtonEnable.connect(self.commitButton.setEnabled)
923
924        self.setWindowTitle('%s - commit' % commit.repo.displayname)
925        self.commit = commit
926        self.commit.reload()
927        self.updateUndo()
928        self.commit.msgte.setFocus()
929        QShortcut(QKeySequence.Refresh, self, self.refresh)
930
931    def linkActivated(self, link):
932        link = hglib.fromunicode(link)
933        if link.startswith('subrepo:'):
934            from tortoisehg.hgqt.run import qtrun
935            qtrun(run, ui.ui(), root=link[8:])
936        if link.startswith('shelve:'):
937            repo = self.commit.repo
938            from tortoisehg.hgqt import shelve
939            dlg = shelve.ShelveDialog(repo, self)
940            dlg.finished.connect(dlg.deleteLater)
941            dlg.exec_()
942            self.refresh()
943
944    def updateUndo(self):
945        BB = QDialogButtonBox
946        undomsg = self.commit.canUndo()
947        if undomsg:
948            self.bb.button(BB.Discard).setEnabled(True)
949            self.bb.button(BB.Discard).setToolTip(undomsg)
950        else:
951            self.bb.button(BB.Discard).setEnabled(False)
952            self.bb.button(BB.Discard).setToolTip('')
953
954    def refresh(self):
955        self.updateUndo()
956        self.commit.reload()
957
958    def postcommit(self):
959        repo = self.commit.stwidget.repo
960        if repo.ui.configbool('tortoisehg', 'closeci'):
961            self.reject()
962            return
963
964    def accept(self):
965        self.commit.commit()
966
967    def reject(self):
968        if self.commit.canExit():
969            s = QSettings()
970            s.setValue('commit/geom', self.saveGeometry())
971            self.commit.saveSettings(s, 'committool')
972            QDialog.reject(self)
973
974def run(ui, *pats, **opts):
975    return CommitDialog(hglib.canonpaths(pats), opts)