Kiln » TortoiseHg » TortoiseHg
Clone URL:  
Pushed to one repository · View In Graph Contained in 1.9, 1.9.1, and 1.9.2

archive: porting to pyqt

Changeset 3f13ee011f16

Parent d0afe4ab9b59

by Johan Samyn

Changes to 2 files · Browse files at 3f13ee011f16 Showing diff from parent d0afe4ab9b59 Diff from another changeset...

Change 1 of 1 Show Entire File tortoisehg/​hgqt/​archive.py Stacked
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
@@ -0,0 +1,373 @@
+# archive.py - TortoiseHg's dialog for archiving a repo revision +# +# Copyright 2009 Emmanuel Rosa <goaway1000@gmail.com> +# Copyright 2010 Johan Samyn <johan.samyn@gmail.com> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2, incorporated herein by reference. + + +# TODO: +# +# - Allow manually adapting the destination path. +# - Keep the selection via the browse btn as a basename (instead of repo.root). +# - Make what is typed in in the lineedit of the rev_combo change dest_edit (and hgcmd_txt) +# - Make what is manually changed in the dest_edit be reflected in the hgcmd. +# - Update the hgcmd lineedit when/after manually changing the dest_edit. +# - Make the hgcmd_txt expand to show it's complete content (multiline eventualy). +# +# - Save/load qt settings (window position, ...). +# +# - See #251 : Make an archive with only the files modified in the selected revision. +# New checkbox "Only files modified/created in this revision". +# You can get the file list for any arbitrary revision with this code: +# ctx = repo[rev] +# files = ctx.modified() + ctx.added() + + +import os + +from PyQt4.QtCore import Qt, SIGNAL, SLOT, QObject, QString, SIGNAL +from PyQt4.QtGui import QDialog, QComboBox, QVBoxLayout, QGridLayout, QLabel +from PyQt4.QtGui import QLineEdit, QPushButton, QLayout, QRadioButton, QButtonGroup +from PyQt4.QtGui import QHBoxLayout, QMessageBox, QIcon, QPixmap, QFileDialog +from PyQt4.QtGui import QCheckBox + +from mercurial import hg, error + +from tortoisehg.util.i18n import _ +from tortoisehg.util import hglib, paths +from tortoisehg.hgqt import cmdui, qtlib +#import tortoisehg.hgqt # import connect +connect = QObject.connect + +WD_PARENT = _('= Working Directory Parent =') + +class ArchiveDialog(QDialog): + """ Dialog to archive a particular Mercurial revision """ + + def __init__(self, ui, repo, rev=None, parent=None): + super(ArchiveDialog, self).__init__(parent=None) + self.ui = ui + self.repo = repo + self.initrev = rev + + # base layout box + self.vbox = QVBoxLayout() + self.vbox.setSpacing(6) + + # main layout grid + self.grid = QGridLayout() + self.grid.setSpacing(6) + self.vbox.addLayout(self.grid) + + # revision combo + self.rev_combo = QComboBox() + self.rev_combo.setEditable(True) + self.rev_combo.setSizeAdjustPolicy(QComboBox.AdjustToContents) + self.rev_lbl = QLabel(_('Archive revision:')) + self.rev_lbl.setAlignment(Qt.AlignRight) + self.grid.addWidget(self.rev_lbl, 0, 0) + self.grid.addWidget(self.rev_combo, 0, 1) + self.mod_in_rev_chk = QCheckBox(_('Only files modified/created in this revision')) + self.grid.addWidget(self.mod_in_rev_chk, 1, 1) + + # destination lineedit and button + self.dest_edit = QLineEdit() + self.dest_edit.setMinimumWidth(300) + self.dest_btn = QPushButton(_('Browse...')) + self.dest_btn.setAutoDefault(False) + self.dest_lbl = QLabel(_('Destination path:')) + self.dest_lbl.setAlignment(Qt.AlignRight) + self.grid.addWidget(self.dest_lbl, 2, 0) + self.grid.addWidget(self.dest_edit, 2, 1) + self.grid.addWidget(self.dest_btn, 2, 2) + + # archive types + self.filesradio = QRadioButton(_('Directory of files'), None) + self.tarradio = QRadioButton(_('Uncompressed tar archive'), None) + self.tbz2radio = QRadioButton(_('Tar archive compressed using bzip2'), None) + self.tgzradio = QRadioButton(_('Tar archive compressed using gzip'), None) + self.uzipradio = QRadioButton(_('Uncompressed zip archive'), None) + self.zipradio = QRadioButton(_('Zip archive compressed using deflate'), None) + + self.types_lbl = QLabel(_('Archive types:')) + self.types_lbl.setAlignment(Qt.AlignRight) + self.grid.addWidget(self.types_lbl, 3, 0) + self.grid.addWidget(self.filesradio, 3, 1) + self.grid.addWidget(self.tarradio, 4, 1) + self.grid.addWidget(self.tbz2radio, 5, 1) + self.grid.addWidget(self.tgzradio, 6, 1) + self.grid.addWidget(self.uzipradio, 7, 1) + self.grid.addWidget(self.zipradio, 8, 1) + + # some extras + self.keep_open_chk = QCheckBox(_('Always show output')) + self.grid.addWidget(self.keep_open_chk, 10, 1) + self.hgcmd_lbl = QLabel(_('Hg command:')) + self.hgcmd_lbl.setAlignment(Qt.AlignRight) + self.hgcmd_txt = QLineEdit() + self.hgcmd_txt.setReadOnly(True) +# self.hgcmd_txt.setGeometry(400, self.hgcmd_txt.height()) + self.grid.addWidget(self.hgcmd_lbl, 9, 0) + self.grid.addWidget(self.hgcmd_txt, 9, 1) + + # command widget + self.cmd = cmdui.Widget() + self.cmd.commandStarted.connect(self.command_started) + self.cmd.commandFinished.connect(self.command_finished) + self.cmd.commandCanceling.connect(self.command_canceling) + self.cmd.setHidden(True) + self.vbox.addWidget(self.cmd) + + # bottom buttons + self.hbox = QHBoxLayout() + self.arch_btn = QPushButton(_('&Archive')) + self.arch_btn.setAutoDefault(False) + self.close_btn = QPushButton(_('&Close')) + self.close_btn.setDefault(True) + self.close_btn.setFocus() + self.detail_btn = QPushButton(_('&Detail')) + self.detail_btn = QPushButton(_('&Detail')) + self.detail_btn.setAutoDefault(False) + self.detail_btn.setHidden(True) + self.cancel_btn = QPushButton(_('Cancel')) + self.cancel_btn.setAutoDefault(False) + self.cancel_btn.setHidden(True) + self.hbox.addWidget(self.detail_btn) + self.hbox.addStretch(0) + self.hbox.addWidget(self.arch_btn) + self.hbox.addWidget(self.close_btn) + self.hbox.addWidget(self.cancel_btn) + self.vbox.addLayout(self.hbox) + self.rev_combo.setFocus() + + # set default values + self.prevtarget = None + if self.initrev: + self.rev_combo.addItem(str(self.initrev)) + else: + self.rev_combo.addItem(WD_PARENT) + self.rev_combo.setCurrentIndex(0) + for b in self.repo.branchtags(): + self.rev_combo.addItem(b) + tags = list(self.repo.tags()) + tags.sort() + tags.reverse() + for t in tags: + self.rev_combo.addItem(t) + self.rev_combo.setMaxVisibleItems(self.rev_combo.count()) + self.dest_edit.setText(self.repo.root) + self.filesradio.setChecked(True) + self.update_path() + + # connecting slots + self.make_connects() + + # dialog setting + self.setWindowTitle(_('TortoiseHg Archive - %s') % self.repo.root) + iconfile = paths.get_tortoise_icon('menucheckout.ico') + icon = QIcon() + icon.addPixmap(QPixmap(iconfile), QIcon.Normal, QIcon.Off) + self.setWindowIcon(icon) + self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) + self.setLayout(self.vbox) + self.layout().setSizeConstraint(QLayout.SetFixedSize) + self.rev_combo.setFocus() + + def get_selected_archive_type(self): + """Return a dictionary describing the selected archive type""" + if self.tarradio.isChecked(): + return {'type': 'tar', 'ext': '.tar', 'label': _('Tar archives')} + elif self.tbz2radio.isChecked(): + return {'type': 'tbz2', 'ext': '.tar.bz2', 'label': _('Bzip2 tar archives')} + elif self.tgzradio.isChecked(): + return {'type': 'tgz', 'ext': '.tar.gz', 'label': _('Gzip tar archives')} + elif self.uzipradio.isChecked(): + return {'type': 'uzip', 'ext': '.zip', 'label': ('Uncompressed zip archives')} + elif self.zipradio.isChecked(): + return {'type': 'zip', 'ext': '.zip', 'label': _('Compressed zip archives')} + return {'type': 'files', 'ext': '', 'label': _('Directory of files')} + + def update_path(self): + def remove_ext(path): + for ext in ('.tar', '.tar.bz2', '.tar.gz', '.zip'): + if path.endswith(ext): + return path.replace(ext, '') + return path + def remove_rev(path): + l = '' + for i in xrange(self.rev_combo.count() - 1): + l += hglib.fromunicode(self.rev_combo.itemText(i)) + revs = [rev[0] for rev in l] + revs.append(wdrev) + if not self.prevtarget is None: + revs.append(self.prevtarget) + for rev in ['_' + rev for rev in revs]: + if path.endswith(rev): + return path.replace(rev, '') + return path + def add_rev(path, rev): + return '%s_%s' % (path, rev) + def add_ext(path): + select = self.get_selected_archive_type() + if select['type'] != 'files': + path += select['ext'] + return path + text = self.rev_combo.currentText() + if len(text) == 0: + return + wdrev = str(self.repo['.'].rev()) + if text == WD_PARENT: + text = wdrev + else: + try: + self.repo[hglib.fromunicode(text)] + except (error.RepoError, error.LookupError): + return + path = hglib.fromunicode(self.dest_edit.text()) + path = remove_ext(path) + path = remove_rev(path) + path = add_rev(path, text) + path = add_ext(path) + self.dest_edit.setText(path) + self.prevtarget = text + type = self.get_selected_archive_type()['type'] + self.compose_command(path, type) + + def make_connects(self): + self.connect(self.rev_combo, SIGNAL('currentIndexChanged(int)'), self.rev_combo_changed) + self.dest_btn.clicked.connect(self.browse_clicked) + self.filesradio.toggled.connect(self.update_path) + self.tarradio.toggled.connect(self.update_path) + self.tbz2radio.toggled.connect(self.update_path) + self.tgzradio.toggled.connect(self.update_path) + self.uzipradio.toggled.connect(self.update_path) + self.zipradio.toggled.connect(self.update_path) + self.arch_btn.clicked.connect(self.archive) + self.detail_btn.clicked.connect(self.detail_clicked) + self.close_btn.clicked.connect(self.close) + + def rev_combo_changed(self, index): + self.update_path() + + def browse_clicked(self): + """Select the destination directory or file""" + dest = hglib.fromunicode(self.dest_edit.text()) + if not os.path.exists(dest): + dest = os.path.dirname(dest) + select = self.get_selected_archive_type() + FD = QFileDialog + if select['type'] == 'files': + caption = _('Select Destination Folder') + path = FD.getExistingDirectory(parent=self, caption=caption, + options=FD.ShowDirsOnly | FD.ReadOnly) + response = str(path) + else: + caption = _('Open File') + ext = '*' + select['ext'] + filter = '%s (%s)\nAll Files (*.*)' % (select['label'], ext) + filename = FD.getOpenFileName(parent=self, caption=caption, + directory=dest, filter=filter, options=FD.ReadOnly ); + response = str(filename) + if response: + self.dest_edit.setText(response) + self.update_path() + + def compose_command(self, dest, type): + cmdline = ['archive'] + rev = hglib.fromunicode(self.rev_combo.currentText()) + if rev != WD_PARENT: + cmdline.append('--rev') + cmdline.append(rev) + cmdline.append('-t') + cmdline.append(type) + cmdline.append('--') + cmdline.append(hglib.fromunicode(dest)) + self.hgcmd_txt.setText(' '.join(cmdline)) +# self.hgcmd_txt.resize(500, self.hgcmd_txt.height()) + return cmdline + + def archive(self): + # verify input + type = self.get_selected_archive_type()['type'] + dest = self.dest_edit.text() + if os.path.exists(dest): + if type == 'files': + if os.path.isfile(dest): + qtlib.WarningMsgBox(_('Duplicate name'), + _('The destination "%s" already exists as a file!' % dest)) + return False + elif os.listdir(dest): + r = qtlib.QuestionMsgBox(_('Confirm overwrite'), + _('The directory "%s" is not empty!\n\n' + 'Do you want to overwrite it?' % dest), + buttons=QMessageBox.Yes|QMessageBox.No) + if r != QMessageBox.Yes: + return False + else: + if os.path.isfile(dest): + r = qtlib.QuestionMsgBox(_('Confirm overwrite'), + _('The file "%s" already exists!\n\n' + 'Do you want to overwrite it?' % dest), + buttons=QMessageBox.Yes|QMessageBox.No) + if r != QMessageBox.Yes: + return False + else: + r = qtlib.WarningMsgBox(_('Duplicate name'), + _('The destination "%s" already exists as a folder!' % dest)) + return False + + # prepare command line + cmdline = self.compose_command(dest, type) + + # start archiving + self.cmd.run(cmdline) + + def detail_clicked(self): + if self.cmd.is_show_output(): + self.cmd.show_output(False) + else: + self.cmd.show_output(True) + + def cancel_clicked(): + self.cmd.cancel() + + def command_started(self): + self.dest_edit.setEnabled(False) + self.rev_combo.setEnabled(False) + self.dest_edit.setEnabled(False) + self.dest_btn.setEnabled(False) + self.filesradio.setEnabled(False) + self.tarradio.setEnabled(False) + self.tbz2radio.setEnabled(False) + self.tgzradio.setEnabled(False) + self.uzipradio.setEnabled(False) + self.zipradio.setEnabled(False) + self.cmd.setShown(True) + self.arch_btn.setHidden(True) + self.close_btn.setHidden(True) + self.cancel_btn.setShown(True) + self.detail_btn.setShown(True) + + def command_finished(self, wrapper): + if wrapper.data is not 0 or self.cmd.is_show_output()\ + or self.keep_open_chk.isChecked(): + if not self.cmd.is_show_output(): + self.detail_btn.click() + self.cancel_btn.setHidden(True) + self.close_btn.setShown(True) + self.close_btn.setAutoDefault(True) + self.close_btn.setFocus() + else: + self.reject() + + def command_canceling(self): + self.cancel_btn.setDisabled(True) + + +def run(ui, *revs, **opts): + rev = opts.get('rev') + repo = hg.repository(ui, paths.find_root()) + return ArchiveDialog(repo.ui, repo, rev) +
 
427
428
429
 
 
 
 
 
430
431
432
 
642
643
644
 
 
 
645
646
647
 
427
428
429
430
431
432
433
434
435
436
437
 
647
648
649
650
651
652
653
654
655
@@ -427,6 +427,11 @@
  from tortoisehg.hgqt.grep import run   qtrun(run, ui, *pats, **opts)   +def archive(ui, *pats, **opts): + """archive dialog""" + from tortoisehg.hgqt.archive import run + qtrun(run, ui, *pats, **opts) +  ### help management, adapted from mercurial.commands.help_()  def help_(ui, name=None, with_version=False, **opts):   """show help for a command, extension, or list of commands @@ -642,6 +647,9 @@
 table = {   "about": (about, [], _('thg about')),   "add": (add, [], _('thg add [FILE]...')), + "archive": (archive, + [('r', 'rev', '', _('revision to archive'))], + _('thg archive')),   "^clone":   (clone,   [('U', 'noupdate', None,