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)

quickop.py 11.1 KB
Newer Older
1 2 3 4 5 6 7
# quickop.py - TortoiseHg's dialog for quick dirstate operations
#
# Copyright 2009 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
9

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
import os
import sys

from .qtcore import (
    QObject,
    QSettings,
    Qt,
)
from .qtgui import (
    QCheckBox,
    QDialog,
    QDialogButtonBox,
    QKeySequence,
    QLabel,
    QHBoxLayout,
    QPushButton,
    QShortcut,
    QVBoxLayout,
)
29

30 31 32 33
from hgext.largefiles import (
    lfutil,
)

34 35
from mercurial import (
    error,
36
    pycompat,
37 38
    util,
)
39

40 41 42 43 44 45 46 47 48 49 50 51
from ..util import (
    hglib,
    shlib,
)
from ..util.i18n import _
from . import (
    cmdcore,
    cmdui,
    lfprompt,
    qtlib,
    status,
)
52

53 54 55 56
LABELS = { 'add': (_('Checkmark files to add'), _('Add')),
           'forget': (_('Checkmark files to forget'), _('Forget')),
           'revert': (_('Checkmark files to revert'), _('Revert')),
           'remove': (_('Checkmark files to remove'), _('Remove')),}
57

58
ICONS = { 'add': 'hg-add',
59
           'forget': 'hg-remove',
60
           'revert': 'hg-revert',
61
           'remove': 'hg-remove',}
62

63
class QuickOpDialog(QDialog):
64
    """ Dialog for performing quick dirstate operations """
65
    def __init__(self, repoagent, command, pats, parent):
66
        QDialog.__init__(self, parent)
67
        self.setWindowFlags(Qt.Window)
68
        self.pats = pats
69
        self._repoagent = repoagent
70 71
        self._cmdsession = cmdcore.nullCmdSession()
        self._cmddialog = cmdui.CmdSessionDialog(self)
72 73 74 75 76 77

        # Handle rm alias
        if command == 'rm':
            command = 'remove'
        self.command = command

78 79
        self.setWindowTitle(_('%s - hg %s')
                            % (repoagent.displayName(), command))
80
        self.setWindowIcon(qtlib.geticon(ICONS[command]))
81

82
        layout = QVBoxLayout()
83
        layout.setContentsMargins(0, 0, 0, 0)
84 85
        self.setLayout(layout)

86 87 88
        toplayout = QVBoxLayout()
        toplayout.setContentsMargins(5, 5, 5, 0)
        layout.addLayout(toplayout)
89

90 91 92 93 94 95 96
        hbox = QHBoxLayout()
        lbl = QLabel(LABELS[command][0])
        slbl = QLabel()
        hbox.addWidget(lbl)
        hbox.addStretch(1)
        hbox.addWidget(slbl)
        self.status_label = slbl
97
        toplayout.addLayout(hbox)
Steve Borho's avatar
Steve Borho committed
98

99 100 101 102 103 104 105
        types = { 'add'    : 'I?',
                  'forget' : 'MAR!C',
                  'revert' : 'MAR!',
                  'remove' : 'MAR!CI?',
                }
        filetypes = types[self.command]

106 107 108 109 110 111 112
        checktypes = { 'add'    : '?',
                       'forget' : '',
                       'revert' : 'MAR!',
                       'remove' : '',
                     }
        defcheck = checktypes[self.command]

113
        opts = {}
114
        for s, val in status.statusTypes.items():
115 116
            opts[val.name] = s in filetypes

117
        opts['checkall'] = True # pre-check all matching files
118
        stwidget = status.StatusWidget(repoagent, pats, opts, self,
119
                                       defcheck=defcheck)
120
        toplayout.addWidget(stwidget, 1)
121

122
        hbox = QHBoxLayout()
123 124
        if self.command == 'revert':
            ## no backup checkbox
125
            chk = QCheckBox(_('Do not save backup files (*.orig)'))
126 127 128 129 130 131
        elif self.command == 'remove':
            ## force checkbox
            chk = QCheckBox(_('Force removal of modified files (--force)'))
        else:
            chk = None
        if chk:
Steve Borho's avatar
Steve Borho committed
132
            self.chk = chk
133
            hbox.addWidget(chk)
134

135 136
        self.statusbar = cmdui.ThgStatusBar(self)
        stwidget.showMessage.connect(self.statusbar.showMessage)
137

138
        BB = QDialogButtonBox
139
        bb = QDialogButtonBox(BB.Ok|BB.Close)
Steve Borho's avatar
Steve Borho committed
140 141
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
142 143
        bb.button(BB.Ok).setDefault(True)
        bb.button(BB.Ok).setText(LABELS[command][1])
144 145 146
        hbox.addStretch()
        hbox.addWidget(bb)
        toplayout.addLayout(hbox)
147
        self.bb = bb
148

149
        if self.command == 'add':
150
            if b'largefiles' in self.repo.extensions():
151 152 153 154 155 156
                self.addLfilesButton = QPushButton(_('Add &Largefiles'))
            else:
                self.addLfilesButton = None
            if self.addLfilesButton:
                self.addLfilesButton.clicked.connect(self.addLfiles)
                bb.addButton(self.addLfilesButton, BB.ActionRole)
157

158
        layout.addWidget(self.statusbar)
159

160
        s = QSettings()
161
        stwidget.loadSettings(s, 'quickop')
162
        self.restoreGeometry(qtlib.readByteArray(s, 'quickop/geom'))
163
        if hasattr(self, 'chk'):
164
            if self.command == 'revert':
165
                self.chk.setChecked(
166
                    qtlib.readBool(s, 'quickop/nobackup', True))
167
            elif self.command == 'remove':
Yuya Nishihara's avatar
Yuya Nishihara committed
168
                self.chk.setChecked(
169
                    qtlib.readBool(s, 'quickop/forceremove', False))
170
        self.stwidget = stwidget
171
        self.stwidget.refreshWctx()
172
        QShortcut(QKeySequence('Ctrl+Return'), self, self.accept)
173
        QShortcut(QKeySequence('Ctrl+Enter'), self, self.accept)
174 175
        qtlib.newshortcutsforstdkey(QKeySequence.Refresh, self,
                                    self.stwidget.refreshWctx)
176
        QShortcut(QKeySequence('Escape'), self, self.reject)
177

178 179 180 181
    @property
    def repo(self):
        return self._repoagent.rawRepo()

182 183 184 185 186
    def _runCommand(self, files, lfiles, opts):
        cmdlines = []
        if files:
            cmdlines.append(hglib.buildcmdargs(self.command, *files, **opts))
        if lfiles:
187
            assert self.command == 'add', self.command
188 189 190 191 192
            lopts = opts.copy()
            lopts['large'] = True
            cmdlines.append(hglib.buildcmdargs(self.command, *lfiles, **lopts))
        self.files = files + lfiles

193
        ucmdlines = [pycompat.maplist(hglib.tounicode, xs) for xs in cmdlines]
194 195 196
        self._cmdsession = sess = self._repoagent.runCommandSequence(ucmdlines,
                                                                     self)
        sess.commandFinished.connect(self.commandFinished)
197
        sess.progressReceived.connect(self.statusbar.setProgress)
198
        self._cmddialog.setSession(sess)
199
        self.bb.button(QDialogButtonBox.Ok).setEnabled(False)
200

201
    def commandFinished(self, ret):
202
        self.bb.button(QDialogButtonBox.Ok).setEnabled(True)
203
        self.statusbar.clearProgress()
Steve Borho's avatar
Steve Borho committed
204 205
        if ret == 0:
            shlib.shell_notify(self.files)
206
            self.reject()
207 208
        else:
            self._cmddialog.show()
209

210
    def accept(self):
211 212
        cmdopts = {}
        if hasattr(self, 'chk'):
213
            if self.command == 'revert':
214
                cmdopts['no_backup'] = self.chk.isChecked()
215
            elif self.command == 'remove':
216
                cmdopts['force'] = self.chk.isChecked()
Steve Borho's avatar
Steve Borho committed
217
        files = self.stwidget.getChecked()
218
        if not files:
Steve Borho's avatar
Steve Borho committed
219 220 221 222
            qtlib.WarningMsgBox(_('No files selected'),
                                _('No operation to perform'),
                                parent=self)
            return
223
        if self.command == 'remove':
224 225 226 227 228 229 230 231
            with lfutil.lfstatus(self.repo):
                try:
                    repostate = self.repo.status()
                except (EnvironmentError, error.Abort) as e:
                    qtlib.WarningMsgBox(_('Unable to read repository status'),
                                        hglib.tounicode(str(e)), parent=self)
                    return

232 233 234
            if not self.chk.isChecked():
                selmodified = []
                for wfile in files:
235
                    if wfile in repostate.modified:
236 237
                        selmodified.append(wfile)
                if selmodified:
Yuya Nishihara's avatar
Yuya Nishihara committed
238 239 240 241 242 243 244 245 246 247
                    prompt = qtlib.CustomPrompt(
                        _('Confirm Remove'),
                        _('You have selected one or more files that have been '
                          'modified.  By default, these files will not be '
                          'removed.  What would you like to do?'),
                        self,
                        (_('Remove &Unmodified Files'),
                         _('Remove &All Selected Files'),
                         _('Cancel')),
                        0, 2, selmodified)
248 249
                    ret = prompt.run()
                    if ret == 1:
250
                        cmdopts['force'] = True
251 252
                    elif ret == 2:
                        return
253
            for wfile in files:
254
                if wfile in repostate.unknown or wfile in repostate.ignored:
255 256 257 258 259
                    try:
                        util.unlink(wfile)
                    except EnvironmentError:
                        pass
                    files.remove(wfile)
260
        elif self.command == 'add':
261
            if b'largefiles' in self.repo.extensions():
262 263
                self.addWithPrompt(files)
                return
264
        if files:
265
            self._runCommand(files, [], cmdopts)
266 267
        else:
            self.reject()
268 269

    def reject(self):
270 271
        if not self._cmdsession.isFinished():
            self._cmdsession.abort()
272 273
        elif not self.stwidget.canExit():
            return
274
        else:
275
            s = QSettings()
276
            self.stwidget.saveSettings(s, 'quickop')
Steve Borho's avatar
Steve Borho committed
277
            s.setValue('quickop/geom', self.saveGeometry())
278
            if hasattr(self, 'chk'):
279 280 281 282
                if self.command == 'revert':
                    s.setValue('quickop/nobackup', self.chk.isChecked())
                elif self.command == 'remove':
                    s.setValue('quickop/forceremove', self.chk.isChecked())
283
            QDialog.reject(self)
284

285
    def addLfiles(self):
286 287 288 289 290 291
        files = self.stwidget.getChecked()
        if not files:
            qtlib.WarningMsgBox(_('No files selected'),
                                _('No operation to perform'),
                                parent=self)
            return
292
        self._runCommand([], files, {})
293 294

    def addWithPrompt(self, files):
295
        result = lfprompt.promptForLfiles(self, self.repo.ui, self.repo, files)
296 297
        if not result:
            return
298
        files, lfiles = result
299
        self._runCommand(files, lfiles, {})
300

301
class HeadlessQuickop(QObject):
302
    def __init__(self, repoagent, cmdline):
303
        QObject.__init__(self)
Steve Borho's avatar
Steve Borho committed
304
        self.files = cmdline[1:]
305
        self._cmddialog = cmdui.CmdSessionDialog()
306
        sess = repoagent.runCommand(pycompat.maplist(hglib.tounicode, cmdline))
307 308
        sess.commandFinished.connect(self.commandFinished)
        self._cmddialog.setSession(sess)
309 310 311

    def commandFinished(self, ret):
        if ret == 0:
Steve Borho's avatar
Steve Borho committed
312
            shlib.shell_notify(self.files)
313
            sys.exit(0)
314 315
        else:
            self._cmddialog.show()
316

317 318 319 320 321 322
    # dummy methods to act as QWidget (see run.qtrun)
    def show(self):
        pass
    def raise_(self):
        pass

323 324
def run(ui, repoagent, *pats, **opts):
    repo = repoagent.rawRepo()
325
    pats = hglib.canonpaths(pats)
326
    command = opts['alias']
327
    imm = repo.ui.config(b'tortoisehg', b'immediate')
328
    if opts.get('headless') or command in imm.lower():
329
        cmdline = [command] + pats
330
        return HeadlessQuickop(repoagent, cmdline)
331
    else:
332
        os.chdir(repo.root)  # for scmutil.match() in StatusThread
333
        return QuickOpDialog(repoagent, command, pats, None)