Kiln » TortoiseHg » TortoiseHg
Clone URL:  
Pushed to one repository · View In Graph Contained in 2.1, 2.1.1, and 2.1.2

Merge with stable

Changeset a2b311fa5405

Parents 2a6db97a7aba

Parents 7c905f0daee9

by Steve Borho

Changes to 33 files · Browse files at a2b311fa5405 Showing diff from parent 2a6db97a7aba 7c905f0daee9 Diff from another changeset...

Change 1 of 1 Show Entire File .hgignore Stacked
 
41
42
43
 
 
 
41
42
43
44
45
@@ -41,3 +41,5 @@
 tortoisehg/hgqt/*_rc.py  tortoisehg/hgqt/*_ui.py  thgw +hgext +mercurial
Change 1 of 2 Show Entire File thg Stacked
 
44
45
46
47
48
49
50
51
 
52
53
54
 
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
 
 
 
44
45
46
 
 
 
 
47
48
49
50
51
 
55
56
57
 
 
 
 
 
 
 
 
58
59
60
61
62
63
64
65
 
66
67
68
69
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
72
@@ -44,11 +44,8 @@
 demandimport.ignore.append('tortoisehg.util.config')  demandimport.ignore.append('icons_rc')  demandimport.enable() -from mercurial import ui as uimod, util -from tortoisehg.util.hgversion import hgversion, checkhgversion -import cStringIO -import traceback   +# Verify we can reach TortoiseHg sources first  try:   import tortoisehg.hgqt.run  except ImportError, e: @@ -58,50 +55,18 @@
  sys.stderr.write("(check your install and PYTHONPATH)\n")   sys.exit(-1)   -ui = uimod.ui() -capt = ui.configbool('tortoisehg', 'stderrcapt', True) - -errors = ('Traceback', 'TypeError', 'NameError', 'AttributeError', - 'NotImplementedError') - -err = checkhgversion(hgversion) -if err: +# Verify we have an acceptable version of Mercurial +from tortoisehg.util.hgversion import hgversion, checkhgversion +errmsg = checkhgversion(hgversion) +if errmsg:   from tortoisehg.hgqt.bugreport import run   from tortoisehg.hgqt.run import qtrun   opts = {}   opts['cmd'] = ' '.join(sys.argv[1:]) - opts['error'] = '\n' + err + '\n' + opts['error'] = '\n' + errmsg + '\n'   opts['nofork'] = True   qtrun(run, ui, **opts)   sys.exit(1)   -if not capt or 'THGDEBUG' in os.environ or '--profile' in sys.argv: - sys.exit(tortoisehg.hgqt.run.dispatch(sys.argv[1:])) -else: - mystderr = cStringIO.StringIO() - origstderr = sys.stderr - sys.stderr = mystderr - ret = 0 - try: - ret = tortoisehg.hgqt.run.dispatch(sys.argv[1:]) - sys.stderr = origstderr - mystderr.seek(0) - for l in mystderr.readlines(): - if l.startswith(errors): - from tortoisehg.hgqt.bugreport import run - from tortoisehg.hgqt.run import qtrun - error = 'Recoverable runtime error (stderr):\n' - error += mystderr.getvalue() - opts = {} - opts['cmd'] = ' '.join(sys.argv[1:]) - opts['error'] = error - opts['nofork'] = True - qtrun(run, ui, **opts) - break - sys.exit(ret) - except: - if sys.exc_info()[0] not in [SystemExit, KeyboardInterrupt]: - sys.stderr = origstderr - traceback.print_exc() - else: - raise SystemExit(ret) +ret = tortoisehg.hgqt.run.dispatch(sys.argv[1:]) +sys.exit(ret)
 
9
10
11
12
 
13
14
15
 
44
45
46
47
48
49
50
 
416
417
418
419
 
420
421
422
 
9
10
11
 
12
13
14
15
 
44
45
46
 
47
48
49
 
415
416
417
 
418
419
420
421
@@ -9,7 +9,7 @@
   from mercurial import ui, error, util   -from tortoisehg.hgqt import visdiff, qtlib, qscilib, wctxactions, thgrepo, lexers +from tortoisehg.hgqt import visdiff, qtlib, qscilib, thgrepo, lexers  from tortoisehg.util import paths, hglib, colormap, thread2  from tortoisehg.hgqt.i18n import _  from tortoisehg.hgqt.grep import SearchWidget @@ -44,7 +44,6 @@
  self.setMarginLineNumbers(1, True)   self.setMarginType(2, QsciScintilla.TextMarginRightJustified)   self.setMouseTracking(True) - self.setFont(qtlib.getfont('fontdiff').font())   self.setContextMenuPolicy(Qt.CustomContextMenu)   self.customContextMenuRequested.connect(self.menuRequest)   @@ -416,7 +415,7 @@
    base, _ = visdiff.snapshot(repo, [wfile], repo[rev])   files = [os.path.join(base, wfile)] - wctxactions.edit(self, repo.ui, repo, files, line, pattern) + qtlib.editfiles(repo, files, line, pattern, self)     @pyqtSlot(unicode, dict)   def _openSearchWidget(self, pattern, opts):
 
8
9
10
11
 
12
13
14
 
147
148
149
 
150
151
152
 
159
160
161
162
163
164
165
166
 
 
 
 
 
 
 
 
 
 
 
 
 
167
168
169
 
8
9
10
 
11
12
13
14
 
147
148
149
150
151
152
153
 
160
161
162
 
163
 
 
 
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
@@ -8,7 +8,7 @@
 import os  import sys   -from mercurial import extensions, ui +from mercurial import extensions  from tortoisehg.util import hglib, version  from tortoisehg.hgqt.i18n import _   @@ -147,6 +147,7 @@
  self._textlabel = QLabel(text, wordWrap=True,   textInteractionFlags=labelflags)   self._textlabel.linkActivated.connect(self._openlink) + self._textlabel.setWordWrap(False)   self.layout().addWidget(self._textlabel)     bb = QDialogButtonBox(QDialogButtonBox.Close, centerButtons=True) @@ -159,11 +160,20 @@
  if ref == '#bugreport':   return BugReport(self._opts, self).exec_()   if ref.startswith('#edit:'): - from tortoisehg.hgqt import wctxactions   fname, lineno = ref[6:].rsplit(':', 1) - # A chicken-egg problem here, we need a ui to get your - # editor in order to repair your ui config file. - wctxactions.edit(self, ui.ui(), None, [fname], lineno, None) + try: + # A chicken-egg problem here, we need a ui to get your + # editor in order to repair your ui config file. + from mercurial import ui as uimod + from tortoisehg.hgqt import qtlib + class FakeRepo(object): + def __init__(self): + self.root = os.getcwd() + self.ui = uimod.ui() + fake = FakeRepo() + qtlib.editfiles(fake, [fname], lineno, parent=self) + except Exception, e: + QDesktopServices.openUrl(QUrl.fromLocalFile(fname))    def run(ui, *pats, **opts):   return BugReport(opts)
 
15
16
17
18
 
19
20
21
 
71
72
73
74
 
75
76
77
 
141
142
143
144
145
 
 
146
147
148
 
338
339
340
341
342
 
 
 
 
 
343
344
345
 
15
16
17
 
18
19
20
21
 
71
72
73
 
74
75
76
77
 
141
142
143
 
 
144
145
146
147
148
 
338
339
340
 
 
341
342
343
344
345
346
347
348
@@ -15,7 +15,7 @@
 from tortoisehg.util import hglib  from tortoisehg.util.patchctx import patchctx  from tortoisehg.hgqt.i18n import _ -from tortoisehg.hgqt import qtlib, thgrepo, qscilib, lexers, wctxactions +from tortoisehg.hgqt import qtlib, thgrepo, qscilib, lexers  from tortoisehg.hgqt import filelistmodel, filelistview, fileview    from PyQt4.QtCore import * @@ -71,7 +71,7 @@
  self.diffbrowse.linkActivated.connect(self.linkActivated)   self.diffbrowse.chunksSelected.connect(self.chunksSelected)   - self.filelist.fileRevSelected.connect(self.displayFile) + self.filelist.fileSelected.connect(self.displayFile)   self.filelist.clearDisplay.connect(self.diffbrowse.clearDisplay)     self.splitter.setStretchFactor(0, 0) @@ -141,8 +141,8 @@
  if isinstance(ctx, patchctx):   path = ctx._path   else: - path = self.repo.wjoin(self.currentFile) - wctxactions.edit(self, self.repo.ui, self.repo, [path]) + path = self.currentFile + qtlib.editfiles(self.repo, [path], parent=self)     def getSelectedFileAndChunks(self):   chunks = self.diffbrowse.curchunks @@ -338,8 +338,11 @@
  header = record.parsepatch(buf)[0]   return [header] + header.hunks   - @pyqtSlot(object, object, object) - def displayFile(self, file, rev, status): + @pyqtSlot(QString, QString) + def displayFile(self, file, status): + if isinstance(file, (unicode, QString)): + file = hglib.fromunicode(file) + status = hglib.fromunicode(status)   if file:   self.currentFile = file   path = self.repo.wjoin(file)
 
21
22
23
 
24
25
26
 
357
358
359
 
 
 
 
 
360
361
362
 
21
22
23
24
25
26
27
 
358
359
360
361
362
363
364
365
366
367
368
@@ -21,6 +21,7 @@
 class CloneDialog(QDialog):     cmdfinished = pyqtSignal(int) + clonedRepository = pyqtSignal(QString)     def __init__(self, args=None, opts={}, parent=None):   super(CloneDialog, self).__init__(parent) @@ -357,6 +358,11 @@
  else:   self.accept()   + if not ret: + # Let the workbench know that a repository has been successfully + # cloned + self.clonedRepository.emit(self.dest_combo.currentText()) +   def onCloseClicked(self):   if self.ret is 0:   self.accept()
 
223
224
225
226
 
227
228
229
 
223
224
225
 
226
227
228
229
@@ -223,7 +223,7 @@
  raise UnknownItem(item)   if 'label' in custom and not kargs.get('usepreset', False):   try: - return custom['label'](widget, item) + return custom['label'](widget, item, ctx)   except UnknownItem:   pass   try:
 
81
82
83
84
 
85
86
 
87
88
89
 
81
82
83
 
84
85
 
86
87
88
89
@@ -81,9 +81,9 @@
  filename = os.path.basename(widget.target)   return filename, revid   raise csinfo.UnknownItem(item) - def labelfunc(widget, item): + def labelfunc(widget, item, ctx):   if item in ('item', 'item_l'): - if not isinstance(widget.ctx, patchctx): + if not isinstance(ctx, patchctx):   return _('Revision:')   return _('Patch:')   raise csinfo.UnknownItem(item)
 
153
154
155
156
157
158
159
 
217
218
219
220
 
221
222
223
 
153
154
155
 
156
157
158
 
216
217
218
 
219
220
221
222
@@ -153,7 +153,6 @@
  vbox.addWidget(self.revpanel, 0)     self.textView = HgFileView(self.repo, self) - self.textView.forceMode('file')   self.textView.revisionSelected.connect(self.goto)   vbox.addWidget(self.textView, 1)   @@ -217,7 +216,7 @@
  pos = self.textView.verticalScrollBar().value()   ctx = self.filerevmodel.repo.changectx(rev)   self.textView.setContext(ctx) - self.textView.displayFile(self.filerevmodel.graph.filename(rev)) + self.textView.displayFile(self.filerevmodel.graph.filename(rev), None)   self.textView.verticalScrollBar().setValue(pos)   self.revpanel.set_revision(rev)   self.revpanel.update(repo = self.repo)
 
42
43
44
45
46
47
48
 
50
51
52
53
54
55
56
57
58
59
60
 
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
 
42
43
44
 
45
46
47
 
49
50
51
 
 
 
 
 
52
53
54
 
81
82
83
 
 
 
 
 
 
 
 
 
 
 
 
84
85
86
@@ -42,7 +42,6 @@
  self._files = []   self._filesdict = {}   self._fulllist = False - self._secondParent = False     @pyqtSlot(bool)   def toggleFullFileList(self, value): @@ -50,11 +49,6 @@
  self.loadFiles()   self.layoutChanged.emit()   - @pyqtSlot(bool) - def toggleSecondParent(self, value): - self._secondParent = value - self.layoutChanged.emit() -   def __len__(self):   return len(self._files)   @@ -87,18 +81,6 @@
  row = index.row()   return self._files[row]['path']   - def revFromIndex(self, index): - 'return revision for index. index is guarunteed to be valid' - if len(self._ctx.parents()) < 2: - return None - row = index.row() - data = self._files[row] - if (data['wasmerged'] and self._secondParent) or \ - (data['parent'] == 1 and self._fulllist): - return self._ctx.p2().rev() - else: - return self._ctx.p1().rev() -   def dataFromIndex(self, index):   if not index.isValid() or index.row()>=len(self) or not self._ctx:   return None
 
18
19
20
21
 
22
23
24
25
26
 
30
31
32
33
 
34
35
36
 
54
55
56
57
 
58
59
60
61
62
63
64
65
 
70
71
72
73
74
75
76
77
78
 
79
80
81
 
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
 
164
165
166
167
168
 
169
170
171
172
 
173
174
175
 
177
178
179
180
181
 
182
183
184
 
216
217
218
219
220
221
222
223
224
225
226
227
 
18
19
20
 
21
22
 
23
24
25
 
29
30
31
 
32
33
34
35
 
53
54
55
 
56
57
58
59
 
 
60
61
62
 
67
68
69
 
 
 
 
 
 
70
71
72
73
 
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
 
154
155
156
 
 
157
158
159
160
 
161
162
163
164
 
166
167
168
 
 
169
170
171
172
 
204
205
206
 
 
 
 
 
 
207
208
209
@@ -18,9 +18,8 @@
   from tortoisehg.util import hglib  from tortoisehg.hgqt.i18n import _ -from tortoisehg.hgqt import qtlib +from tortoisehg.hgqt import qtlib, visdiff, revert  from tortoisehg.hgqt.filedialogs import FileLogDialog, FileDiffDialog -from tortoisehg.hgqt import visdiff, wctxactions, revert    from PyQt4.QtCore import *  from PyQt4.QtGui import * @@ -30,7 +29,7 @@
  A QTableView for displaying a HgFileListModel   """   - fileRevSelected = pyqtSignal(object, object, object) + fileSelected = pyqtSignal(QString, QString)   clearDisplay = pyqtSignal()   contextmenu = None   @@ -54,12 +53,10 @@
  QTableView.setModel(self, model)   model.layoutChanged.connect(self.layoutChanged)   model.contextChanged.connect(self.contextChanged) - self.selectionModel().currentRowChanged.connect(self.fileSelected) + self.selectionModel().currentRowChanged.connect(self.onRowChange)   self.horizontalHeader().setResizeMode(1, QHeaderView.Stretch)   self.actionShowAllMerge.setChecked(False)   self.actionShowAllMerge.toggled.connect(model.toggleFullFileList) - self.actionSecondParent.setChecked(False) - self.actionSecondParent.toggled.connect(model.toggleSecondParent)   if model._ctx is not None:   self.contextChanged(model._ctx)   @@ -70,12 +67,7 @@
  self._actions[act].setEnabled(real)   for act in ['diff', 'revert']:   self._actions[act].setEnabled(real or wd) - if len(ctx.parents()) == 2: - self.actionShowAllMerge.setEnabled(True) - self.actionSecondParent.setEnabled(True) - else: - self.actionShowAllMerge.setEnabled(False) - self.actionSecondParent.setEnabled(False) + self.actionShowAllMerge.setEnabled(len(ctx.parents()) == 2)     def currentFile(self):   index = self.currentIndex() @@ -86,37 +78,35 @@
  index = self.currentIndex()   count = len(self.model())   if index.row() == -1: - # index is changing, fileSelected() called for us + # index is changing, onRowChange() called for us   self.selectRow(0)   elif index.row() >= count:   if count: - # index is changing, fileSelected() called for us + # index is changing, onRowChange() called for us   self.selectRow(count-1)   else:   self.clearDisplay.emit() - self.actionSecondParent.setEnabled(False)   else:   # redisplay previous row - self.fileSelected() + self.onRowChange(index)   - def fileSelected(self, index=None, *args): + def onRowChange(self, index, *args):   if index is None:   index = self.currentIndex()   data = self.model().dataFromIndex(index)   if data: - fromRev = self.model().revFromIndex(index) - self.fileRevSelected.emit(data['path'], fromRev, data['status']) - self.actionSecondParent.setEnabled(data['wasmerged']) + self.fileSelected.emit(data['path'], data['status'])   else:   self.clearDisplay.emit() - self.actionSecondParent.setEnabled(False)     def selectFile(self, filename):   'Select given file, if found, else the first file'   index = self.model().indexFromFile(filename)   if index: - self.setCurrentIndex(index) - self.fileSelected(index) + if index != self.currentIndex(): + self.setCurrentIndex(index) + else: + self.onRowChange(index)   elif self.model().count():   self.selectRow(0)   @@ -164,12 +154,11 @@
  repo = model.repo   rev = model._ctx.rev()   if rev is None: - files = [repo.wjoin(filename)] - wctxactions.edit(self, repo.ui, repo, files) + qtlib.editfiles(repo, [filename], parent=self)   else:   base, _ = visdiff.snapshot(repo, [filename], repo[rev])   files = [os.path.join(base, filename)] - wctxactions.edit(self, repo.ui, repo, files) + qtlib.editfiles(repo, files, parent=self)     def editlocal(self):   filename = self.currentFile() @@ -177,8 +166,7 @@
  return   model = self.model()   repo = model.repo - path = repo.wjoin(filename) - wctxactions.edit(self, repo.ui, repo, [path]) + qtlib.editfiles(repo, [filename], parent=self)     def revertfile(self):   filename = self.currentFile() @@ -216,12 +204,6 @@
  _('Toggle display of all files and the direction they were merged'))   self.actionShowAllMerge.setCheckable(True)   self.actionShowAllMerge.setChecked(False) - self.actionSecondParent = QAction(_('Other'), self) - self.actionSecondParent.setToolTip( - _('Toggle display of diffs to second (other) parent')) - self.actionSecondParent.setCheckable(True) - self.actionSecondParent.setChecked(False) - self.actionSecondParent.setEnabled(False)     self._actions = {}   for name, desc, icon, key, tip, cb in [
 
27
28
29
30
 
31
32
33
 
35
36
37
 
 
 
 
38
39
40
 
135
136
137
 
138
139
140
 
142
143
144
 
145
146
147
148
 
149
150
151
 
152
153
 
154
155
156
 
167
168
169
 
170
171
 
 
 
 
 
 
 
 
 
 
 
 
 
172
173
174
 
181
182
183
 
 
 
184
185
186
 
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
 
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
 
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
 
337
338
339
340
341
342
343
344
345
 
 
 
 
 
 
 
346
347
348
 
349
350
351
 
359
360
361
362
 
363
364
365
 
392
393
394
395
 
 
396
397
398
 
401
402
403
404
 
405
406
407
 
457
458
459
460
 
461
462
463
 
474
475
476
477
 
478
479
480
 
544
545
546
 
 
547
548
549
 
27
28
29
 
30
31
32
33
 
35
36
37
38
39
40
41
42
43
44
 
139
140
141
142
143
144
145
 
147
148
149
150
151
152
153
154
155
156
 
 
157
158
159
160
161
162
163
 
174
175
176
177
178
 
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
 
201
202
203
204
205
206
207
208
209
 
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
 
317
318
319
 
 
 
 
 
320
321
322
 
 
323
324
325
326
327
 
328
329
330
 
 
331
332
333
 
 
334
335
336
337
 
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
374
 
378
379
380
 
 
 
 
 
 
381
382
383
384
385
386
387
388
389
 
390
391
392
393
 
401
402
403
 
404
405
406
407
 
434
435
436
 
437
438
439
440
441
 
444
445
446
 
447
448
449
450
 
500
501
502
 
503
504
505
506
 
517
518
519
 
520
521
522
523
 
587
588
589
590
591
592
593
594
@@ -27,7 +27,7 @@
 from tortoisehg.util import hglib, patchctx  from tortoisehg.hgqt.i18n import _  from tortoisehg.hgqt import annotate, qscilib, qtlib, blockmatcher, lexers -from tortoisehg.hgqt import visdiff, wctxactions +from tortoisehg.hgqt import visdiff    from PyQt4.QtCore import *  from PyQt4.QtGui import * @@ -35,6 +35,10 @@
   qsci = Qsci.QsciScintilla   +DiffMode = 1 +FileMode = 2 +AnnMode = 3 +  class HgFileView(QFrame):   """file diff and content viewer"""   @@ -135,6 +139,7 @@
  self._filename = None   self._status = None   self._mode = None + self._parent = 0   self._lostMode = None   self._lastSearch = u'', False   @@ -142,15 +147,17 @@
  _('View change as unified diff output'),   self)   self.actionDiffMode.setCheckable(True) + self.actionDiffMode._mode = DiffMode   self.actionFileMode = QAction(qtlib.geticon('view-file'),   _('View change in context of file'),   self)   self.actionFileMode.setCheckable(True) + self.actionFileMode._mode = FileMode   self.actionAnnMode = QAction(qtlib.geticon('view-annotate'), - _('View change in context, annotate with ' - 'revision number'), + _('annotate with revision numbers'),   self)   self.actionAnnMode.setCheckable(True) + self.actionAnnMode._mode = AnnMode     self.modeToggleGroup = QActionGroup(self)   self.modeToggleGroup.addAction(self.actionDiffMode) @@ -167,8 +174,21 @@
  'Previous diff (alt+up)', self)   self.actionPrevDiff.setShortcut('Alt+Up')   self.actionPrevDiff.triggered.connect(self.prevDiff) + self.setMode(self.actionDiffMode)   - self.forceMode('diff') + self.actionFirstParent = QAction('1', self) + self.actionFirstParent.setCheckable(True) + self.actionFirstParent.setChecked(True) + self.actionFirstParent.setShortcut('CTRL+1') + self.actionFirstParent.setToolTip(_('Show changes from first parent')) + self.actionSecondParent = QAction('2', self) + self.actionSecondParent.setCheckable(True) + self.actionSecondParent.setShortcut('CTRL+2') + self.actionSecondParent.setToolTip(_('Show changes from second parent')) + self.parentToggleGroup = QActionGroup(self) + self.parentToggleGroup.addAction(self.actionFirstParent) + self.parentToggleGroup.addAction(self.actionSecondParent) + self.parentToggleGroup.triggered.connect(self.setParent)     self.actionFind = self.searchbar.toggleViewAction()   self.actionFind.setIcon(qtlib.geticon('edit-find')) @@ -181,6 +201,9 @@
  self.actionShelf.triggered.connect(self.launchShelve)     tb = self.diffToolbar + tb.addAction(self.actionFirstParent) + tb.addAction(self.actionSecondParent) + tb.addSeparator()   tb.addAction(self.actionDiffMode)   tb.addAction(self.actionFileMode)   tb.addAction(self.actionAnnMode) @@ -218,52 +241,72 @@
  @pyqtSlot(QAction)   def setMode(self, action):   'One of the mode toolbar buttons has been toggled' - - mode = {self.actionDiffMode.text():'diff', - self.actionFileMode.text():'file', - self.actionAnnMode.text() :'ann'}[action.text()] - self.actionNextDiff.setEnabled(mode == 'file') - self.actionPrevDiff.setEnabled(False) - self.blk.setVisible(mode == 'file') - self.sci.setAnnotationEnabled(mode == 'ann') + mode = action._mode   if mode != self._mode:   self._mode = mode - if not self._lostMode: - self.displayFile() + self.actionNextDiff.setEnabled(False) + self.actionPrevDiff.setEnabled(False) + self.blk.setVisible(mode == FileMode) + self.sci.setAnnotationEnabled(mode == AnnMode) + self.displayFile(self._filename, self._status)   - def forceMode(self, mode): - 'Force into file or diff mode, based on content constaints' - assert mode in ('diff', 'file') + @pyqtSlot(QAction) + def setParent(self, action): + if action.text() == '1': + parent = 0 + else: + parent = 1 + if self._parent != parent: + self._parent = parent + self.displayFile(self._filename, self._status) + + def restrictModes(self, candiff, canfile, canann): + 'Disable modes based on content constraints' + self.actionDiffMode.setEnabled(candiff) + self.actionFileMode.setEnabled(canfile) + self.actionAnnMode.setEnabled(canann) + + # Switch mode if necessary + mode = self._mode + if not candiff and mode == DiffMode and canfile: + mode = FileMode + if not canfile and mode != DiffMode: + mode = DiffMode   if self._lostMode is None:   self._lostMode = self._mode - self._mode = mode - if mode == 'diff': + if self._mode != mode: + self.actionNextDiff.setEnabled(False) + self.actionPrevDiff.setEnabled(False) + self.blk.setVisible(mode == FileMode) + self.sci.setAnnotationEnabled(mode == AnnMode) + self._mode = mode + + if self._mode == DiffMode:   self.actionDiffMode.setChecked(True) + elif self._mode == FileMode: + self.actionFileMode.setChecked(True)   else: - self.actionFileMode.setChecked(True) - self.actionDiffMode.setEnabled(False) - self.actionFileMode.setEnabled(False) - self.actionAnnMode.setEnabled(False) - self.actionNextDiff.setEnabled(False) - self.actionPrevDiff.setEnabled(False) - self.blk.setVisible(mode == 'file') - self.sci.setAnnotationEnabled(False) + self.actionAnnMode.setChecked(True)     def setContext(self, ctx):   self._ctx = ctx - self._p_rev = None   self.sci.setTabWidth(ctx._repo.tabwidth)   self.actionAnnMode.setVisible(ctx.rev() != None)   self.actionShelf.setVisible(ctx.rev() == None) + self.actionFirstParent.setVisible(len(ctx.parents()) == 2) + self.actionSecondParent.setVisible(len(ctx.parents()) == 2) + self.actionFirstParent.setEnabled(len(ctx.parents()) == 2) + self.actionSecondParent.setEnabled(len(ctx.parents()) == 2)   - def displayDiff(self, rev): - if rev != self._p_rev: - self.displayFile(rev=rev) + def showLine(self, line): + if line < self.sci.lines(): + self.sci.setCursorPosition(line, 0)     @pyqtSlot()   def clearDisplay(self):   self._filename = None - self.forceMode('diff') + self.restrictModes(False, False, False) + self.sci.setMarginWidth(1, 0)   self.clearMarkup()     def clearMarkup(self): @@ -274,26 +317,21 @@
  self.filenamelabel.setText(' ')   self.extralabel.hide()   - def displayFile(self, filename=None, rev=None, status=None): - if filename is None: - filename, status = self._filename, self._status - else: - self._filename, self._status = filename, status + def displayFile(self, filename, status):   if isinstance(filename, (unicode, QString)):   filename = hglib.fromunicode(filename) - if rev is not None: - self._p_rev = rev + status = hglib.fromunicode(status) + self._filename, self._status = filename, status     self.clearMarkup()   if filename is None: - self.forceMode('file') + self.restrictModes(False, False, False)   return   - if self._p_rev is not None: - ctx2 = self.repo[self._p_rev] + if self._parent == 0 or len(self._ctx.parents()) == 1: + ctx2 = self._ctx.p1()   else: - ctx2 = None - + ctx2 = self._ctx.p2()   fd = FileData(self._ctx, ctx2, filename, status)     if fd.elabel: @@ -305,29 +343,32 @@
    if not fd.isValid():   self.sci.setText(fd.error) - self.forceMode('file') + self.restrictModes(False, False, False)   return   - if fd.diff and not fd.contents: - self.forceMode('diff') - elif fd.contents and not fd.diff: - self.forceMode('file') - elif not fd.contents and not fd.diff: - self.forceMode('file') + candiff = bool(fd.diff) + canfile = bool(fd.contents) + canann = canfile and type(self._ctx.rev()) is int + + if not candiff or not canfile: + self.restrictModes(candiff, canfile, canann)   else:   self.actionDiffMode.setEnabled(True)   self.actionFileMode.setEnabled(True)   self.actionAnnMode.setEnabled(True)   if self._lostMode: - if self._lostMode == 'diff': + self._mode = self._lostMode + if self._lostMode == DiffMode:   self.actionDiffMode.trigger() - elif self._lostMode == 'file': + elif self._lostMode == FileMode:   self.actionFileMode.trigger() - elif self._lostMode == 'ann': + elif self._lostMode == AnnMode:   self.actionAnnMode.trigger()   self._lostMode = None + self.blk.setVisible(self._mode == FileMode) + self.sci.setAnnotationEnabled(self._mode == AnnMode)   - if self._mode == 'diff': + if self._mode == DiffMode:   self.sci.setMarginWidth(1, 0)   lexer = lexers.get_diff_lexer(self)   self.sci.setLexer(lexer) @@ -337,15 +378,16 @@
  # diff -r f6bfc41af6d7 -r c1b18806486d tortoisehg/hgqt/thgrepo.py   # --- a/tortoisehg/hgqt/thgrepo.py   # +++ b/tortoisehg/hgqt/thgrepo.py - out = fd.diff.split('\n', 3) - if len(out) == 4: - self.sci.setText(hglib.tounicode(out[3])) - else: - # there was an error or rename without diffs - self.sci.setText(hglib.tounicode(fd.diff)) + if fd.diff: + out = fd.diff.split('\n', 3) + if len(out) == 4: + self.sci.setText(hglib.tounicode(out[3])) + else: + # there was an error or rename without diffs + self.sci.setText(hglib.tounicode(fd.diff))   elif fd.contents is None:   return - elif self._mode == 'ann': + elif self._mode == AnnMode:   self.sci.setSource(filename, self._ctx.rev())   else:   lexer = lexers.get_lexer(filename, fd.contents, self) @@ -359,7 +401,7 @@
  uf = hglib.tounicode(self._filename)   self.fileDisplayed.emit(uf, fd.contents or QString())   - if self._mode == 'file' and fd.contents and fd.olddata: + if self._mode == FileMode and fd.contents and fd.olddata:   # Update blk margin   if self.timer.isActive():   self.timer.stop() @@ -392,7 +434,8 @@
  @pyqtSlot(unicode, object)   @pyqtSlot(unicode, object, int)   def sourceChanged(self, path, rev, line=None): - self.revisionSelected.emit(rev) + if rev != self._ctx.rev() and type(rev) is int: + self.revisionSelected.emit(rev)     @pyqtSlot(unicode, object, int)   def editSelected(self, path, rev, line): @@ -401,7 +444,7 @@
  base = visdiff.snapshot(self.repo, [path], self.repo[rev])[0]   files = [os.path.join(base, path)]   pattern = hglib.fromunicode(self._lastSearch[0]) - wctxactions.edit(self, self.repo.ui, self.repo, files, line, pattern) + qtlib.editfiles(self.repo, files, line, pattern, self)     @pyqtSlot(unicode, bool, bool, bool)   def find(self, exp, icase=True, wrap=False, forward=True): @@ -457,7 +500,7 @@
  self.blk.setUpdatesEnabled(True)     def nextDiff(self): - if self._mode == 'diff' or not self._diffs: + if self._mode == DiffMode or not self._diffs:   self.actionNextDiff.setEnabled(False)   self.actionPrevDiff.setEnabled(False)   return @@ -474,7 +517,7 @@
  self.actionPrevDiff.setEnabled(True)     def prevDiff(self): - if self._mode == 'diff' or not self._diffs: + if self._mode == DiffMode or not self._diffs:   self.actionNextDiff.setEnabled(False)   self.actionPrevDiff.setEnabled(False)   return @@ -544,6 +587,8 @@
  return 'A'   if wfile in removed:   return 'R' + if wfile in ctx: + return 'C'   return None     repo = ctx._repo
 
636
637
638
639
640
641
642
 
646
647
648
649
650
 
651
652
653
654
 
655
656
657
 
636
637
638
 
639
640
641
 
645
646
647
 
 
648
649
650
651
 
652
653
654
655
@@ -636,7 +636,6 @@
  return     def onViewFile(self): - from tortoisehg.hgqt import wctxactions   repo, ui, pattern = self.repo, self.repo.ui, self.pattern   seen = set()   for rev, path, line in self.selectedRows: @@ -646,12 +645,11 @@
  else:   seen.add(path)   if rev is None: - files = [repo.wjoin(path)] - wctxactions.edit(self, ui, repo, files, line, pattern) + qtlib.editfiles(repo, [path], line, pattern, self)   else:   base, _ = visdiff.snapshot(repo, [path], repo[rev])   files = [os.path.join(base, path)] - wctxactions.edit(self, ui, repo, files, line, pattern) + qtlib.editfiles(repo, files, line, pattern, self)     def onVisualDiff(self):   rows = self.selectedRows[:]
 
18
19
20
21
22
 
 
23
24
25
 
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
 
55
56
57
 
81
82
83
84
 
85
86
87
 
93
94
95
96
 
97
98
99
 
104
105
106
107
 
108
109
110
111
112
113
114
115
116
117
118
 
150
151
152
153
154
155
156
 
 
 
157
158
159
160
161
162
163
164
165
 
 
166
167
168
 
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
 
252
253
254
255
256
 
257
258
259
260
261
 
262
263
264
265
266
267
 
268
269
270
 
357
358
359
 
360
361
362
 
371
372
373
374
375
 
376
377
378
379
380
381
382
383
384
 
 
 
 
 
 
 
 
 
 
385
386
387
388
389
390
 
 
 
 
 
391
392
393
 
395
396
397
398
399
400
401
402
403
 
 
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
 
439
440
441
442
 
443
444
445
 
18
19
20
 
 
21
22
23
24
25
 
39
40
41
 
42
43
44
45
 
 
 
 
 
 
46
 
47
48
49
50
 
74
75
76
 
77
78
79
80
 
86
87
88
 
89
90
91
92
 
97
98
99
 
100
101
102
103
104
105
 
 
 
106
107
108
 
140
141
142
 
143
 
144
145
146
147
148
 
 
 
 
 
 
 
 
149
150
151
152
153
 
165
166
167
 
 
 
 
 
 
 
 
 
 
 
 
168
169
170
 
225
226
227
 
 
228
229
230
231
232
 
233
234
235
236
237
 
 
238
239
240
241
 
328
329
330
331
332
333
334
 
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
 
375
376
377
 
 
 
 
 
 
378
379
380
381
382
383
384
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
386
387
 
394
395
396
 
397
398
399
400
@@ -18,8 +18,8 @@
 from tortoisehg.util import paths, hglib    from tortoisehg.hgqt.i18n import _ -from tortoisehg.hgqt import qtlib, qscilib, annotate, status, thgrepo -from tortoisehg.hgqt import visdiff, wctxactions, revert +from tortoisehg.hgqt import qtlib, qscilib, fileview, status, thgrepo +from tortoisehg.hgqt import visdiff, revert  from tortoisehg.hgqt.filedialogs import FileLogDialog, FileDiffDialog  from tortoisehg.hgqt.manifestmodel import ManifestModel   @@ -39,19 +39,12 @@
  self._manifest_widget = ManifestWidget(repo, rev)   self._manifest_widget.revChanged.connect(self._updatewindowtitle)   self._manifest_widget.pathChanged.connect(self._updatewindowtitle) - self._manifest_widget.editSelected.connect(self._openInEditor)   self._manifest_widget.grepRequested.connect(self._openSearchWidget)   self.setCentralWidget(self._manifest_widget)   self.addToolBar(self._manifest_widget.toolbar)   - self._searchbar = qscilib.SearchToolBar() - connectsearchbar(self._manifest_widget, self._searchbar) - self.addToolBar(self._searchbar) - QShortcut(QKeySequence.Find, self, - lambda: self._searchbar.setFocus(Qt.OtherFocusReason)) -   self.setStatusBar(QStatusBar()) - self._manifest_widget.revisionHint.connect(self.statusBar().showMessage) + self._manifest_widget.showMessage.connect(self.statusBar().showMessage)     self._readsettings()   self._updatewindowtitle() @@ -81,7 +74,7 @@
    def setSearchPattern(self, text):   """Set search pattern [unicode]""" - self._searchbar.setPattern(text) + self._manifest_widget._fileview.searchbar.setPattern(text)     @pyqtSlot(unicode, dict)   def _openSearchWidget(self, pattern, opts): @@ -93,7 +86,7 @@
  def _openInEditor(self, path, rev, line):   """Open editor to show the specified file"""   _openineditor(self._repo, path, rev, line, - pattern=self._searchbar.pattern(), parent=self) + pattern=self._fileview.searchbar.pattern(), parent=self)    class ManifestWidget(QWidget):   """Display file tree and contents at the specified revision""" @@ -104,15 +97,12 @@
  pathChanged = pyqtSignal(unicode)   """Emitted (path) when the current file path changed"""   - revisionHint = pyqtSignal(unicode) + showMessage = pyqtSignal(unicode)   """Emitted when to show revision summary as a hint"""     searchRequested = pyqtSignal(unicode)   """Emitted (pattern) when user request to search content"""   - editSelected = pyqtSignal(unicode, object, int) - """Emitted (path, rev, line) when user requests to open editor""" -   grepRequested = pyqtSignal(unicode, dict)   """Emitted (pattern, opts) when user request to search changelog"""   @@ -150,19 +140,14 @@
  navlayoutw = QWidget()   navlayoutw.setLayout(navlayout)   - self._contentview = QStackedWidget()   self._splitter.addWidget(navlayoutw) - self._splitter.addWidget(self._contentview)   self._splitter.setStretchFactor(0, 1) + + self._fileview = fileview.HgFileView(self._repo, self) + self._splitter.addWidget(self._fileview)   self._splitter.setStretchFactor(1, 3) - - self._nullcontent = QWidget() - self._contentview.addWidget(self._nullcontent) - self._fileview = annotate.AnnotateView(self._repo) - self._fileview.sourceChanged.connect(self.setSource) - self._contentview.addWidget(self._fileview) - for name in ('revisionHint', 'searchRequested', 'editSelected', - 'grepRequested'): + self._fileview.revisionSelected.connect(self.setRev) + for name in ('showMessage', 'searchRequested', 'grepRequested'):   getattr(self._fileview, name).connect(getattr(self, name))     def loadSettings(self, qs, prefix): @@ -180,18 +165,6 @@
  statustext='MAC', text=_('Status'))   self._toolbar.addWidget(self._statusfilter)   - self._action_annotate_mode = QAction(_('Annotate'), self, checkable=True) - self._action_annotate_mode.toggled.connect( - self._fileview.setAnnotationEnabled) - self._action_annotate_mode.setEnabled(self.rev is not None) - self._toolbar.addAction(self._action_annotate_mode) - - if hasattr(self, '_searchbar'): - self._action_find = self._searchbar.toggleViewAction() - self._action_find.setIcon(qtlib.geticon('edit-find')) - self._action_find.setShortcut(QKeySequence.Find) - self._toolbar.addAction(self._action_find) -   self._actions = {}   for name, desc, icon, key, tip, cb in [   ('navigate', _('File history'), 'hg-log', 'Shift+Return', @@ -252,19 +225,17 @@
  if self.path is None:   return   if self.rev is None: - files = [self._repo.wjoin(self.path)] - wctxactions.edit(self, self._repo.ui, self._repo, files) + qtlib.editfiles(self._repo, [self.path], parent=self)   else:   base, _ = visdiff.snapshot(self._repo, [self.path],   self._repo[self.rev])   files = [os.path.join(base, self.path)] - wctxactions.edit(self, self._repo.ui, self._repo, files) + qtlib.editfiles(self._repo, files, parent=self)     def editlocal(self):   if self.path is None:   return - path = self._repo.wjoin(self.path) - wctxactions.edit(self, self._repo.ui, self._repo, [path]) + qtlib.editfiles(self._repo, [self.path], parent=self)     def revertfile(self):   if self.path is None: @@ -357,6 +328,7 @@
  """Return current revision"""   return self._rev   + @pyqtSlot(int)   @pyqtSlot(object)   def setRev(self, rev):   """Change revision to show""" @@ -371,23 +343,31 @@
  @pyqtSlot(unicode, object, int)   def setSource(self, path, rev, line=None):   """Change path and revision to show at once""" - revchanged = self._rev != rev - if revchanged: + if self._rev != rev:   self._rev = rev   self._setupmodel() - self.setPath(path) - if self.path in self._repo[rev]: - self._fileview.setSource(path, rev, line) - if revchanged: - # annotate working copy is not supported - self._action_annotate_mode.setEnabled(rev is not None)   self.revChanged.emit(rev) + elif path != self.path: + self.setPath(path) + ctx = self._repo[rev] + if self.path in ctx: + self._fileview.setContext(ctx) + self._fileview.displayFile(path, self.status) + if line: + self._fileview.showLine(int(line) - 1) + else: + self._fileview.clearDisplay()     @property   def path(self):   """Return currently selected path"""   return self._treemodel.filePath(self._treeview.currentIndex())   + @property + def status(self): + """Return currently selected path""" + return self._treemodel.fileStatus(self._treeview.currentIndex()) +   @pyqtSlot(unicode)   def setPath(self, path):   """Change path to show""" @@ -395,38 +375,13 @@
    @pyqtSlot()   def _updatecontent(self): - if hglib.fromunicode(self.path) not in self._repo[self._rev]: - self._contentview.setCurrentWidget(self._nullcontent) - return - - self._contentview.setCurrentWidget(self._fileview) - self._fileview.setSource(self.path, self._rev) + self._fileview.setContext(self._repo[self._rev]) + self._fileview.displayFile(self.path, self.status)     @pyqtSlot()   def _emitPathChanged(self):   self.pathChanged.emit(self.path)   -class ManifestTaskWidget(ManifestWidget): - """Manifest widget designed for task tab""" - - def __init__(self, repo, rev, parent): - super(ManifestTaskWidget, self).__init__(repo, rev, parent) - self.editSelected.connect(self._openInEditor) - - @util.propertycache - def _searchbar(self): - searchbar = qscilib.SearchToolBar(hidable=True) - searchbar.hide() - self.layout().addWidget(searchbar) - connectsearchbar(self, searchbar) - return searchbar - - @pyqtSlot(unicode, object, int) - def _openInEditor(self, path, rev, line): - """Open editor to show the specified file""" - _openineditor(self._repo, path, rev, line, - pattern=self._searchbar.pattern(), parent=self) -  def connectsearchbar(manifestwidget, searchbar):   """Connect searchbar to manifest widget"""   searchbar.conditionChanged.connect(manifestwidget.highlightText) @@ -439,7 +394,7 @@
  pattern = hglib.fromunicode(pattern)   base = visdiff.snapshot(repo, [path], repo[rev])[0]   files = [os.path.join(base, path)] - wctxactions.edit(parent, repo.ui, repo, files, line, pattern) + qtlib.editfiles(repo, files, line, pattern, parent=self)      def run(ui, *pats, **opts):
 
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
 
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
 
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
 
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
 
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
 
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
 
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
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
 
 
 
425
426
427
428
429
430
 
 
 
431
432
433
 
 
 
434
435
 
436
437
438
439
 
 
440
441
442
 
 
 
 
 
 
 
 
443
444
445
446
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
448
449
450
451
 
452
453
454
455
456
457
 
458
459
460
 
465
466
467
468
469
 
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
 
500
501
502
503
504
 
505
506
507
508
509
510
511
512
513
514
 
 
515
516
517
518
519
520
521
522
523
524
525
526
 
 
 
527
528
529
 
 
 
530
531
532
533
534
535
536
537
538
539
540
541
542
543
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
545
546
547
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
548
549
550
551
552
553
554
555
556
557
558
559
 
 
560
561
562
563
564
565
566
567
568
569
 
 
 
570
571
572
 
573
574
575
 
614
615
616
617
618
619
620
621
622
623
624
 
 
 
 
 
625
626
627
628
629
630
631
632
633
634
635
636
637
638
 
 
 
 
 
 
 
 
 
 
 
 
 
 
639
640
641
 
644
645
646
647
648
649
650
651
652
 
 
 
 
 
 
 
 
 
 
 
653
654
 
655
656
657
 
 
658
659
660
 
 
 
 
 
 
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
 
682
683
684
 
 
 
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
 
 
703
704
705
706
707
708
709
 
 
710
711
 
 
 
712
713
714
715
 
 
716
717
 
 
 
 
718
719
720
721
722
723
724
725
726
 
 
 
 
 
 
727
728
729
730
731
732
 
 
 
 
 
 
733
734
735
 
 
 
 
 
 
 
 
 
 
 
 
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
 
 
762
763
764
 
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
 
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
 
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
 
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
 
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
 
194
195
196
 
 
 
197
198
199
 
 
200
201
202
203
 
 
 
 
204
205
206
207
 
208
209
210
211
 
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
 
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
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
 
 
 
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
 
 
 
 
 
412
413
414
415
 
 
416
417
418
 
 
 
 
 
 
 
 
 
419
420
421
422
423
 
424
425
426
427
 
466
467
468
 
469
 
 
 
 
 
 
470
471
472
473
474
475
476
477
 
 
 
 
 
 
 
 
 
 
 
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
 
497
498
499
 
500
 
 
 
 
501
502
503
504
505
506
507
508
509
510
511
512
 
513
514
 
 
515
516
517
 
 
518
519
520
521
522
523
524
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
526
527
 
 
528
529
 
 
530
531
532
533
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
534
535
536
537
538
 
 
 
 
539
540
541
 
542
543
544
545
 
 
 
546
547
548
 
549
550
551
552
553
 
 
 
 
 
 
 
 
554
555
556
557
558
559
560
 
 
 
561
 
562
563
564
565
566
567
568
 
 
569
570
571
572
573
574
575
576
577
578
579
580
581
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
583
584
585
586
@@ -1,49 +1,50 @@
 # merge.py - Merge dialog for TortoiseHg  #  # Copyright 2010 Yuki KODAMA <endflow.net@gmail.com> +# Copyright 2011 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.   -from PyQt4.QtCore import * -from PyQt4.QtGui import * - -from mercurial import error -from mercurial import merge as mergemod +from mercurial import hg, error    from tortoisehg.util import hglib  from tortoisehg.hgqt.i18n import _  from tortoisehg.hgqt import qtlib, csinfo, i18n, cmdui, status, commit, resolve  from tortoisehg.hgqt import qscilib, thgrepo   -keep = i18n.keepgettext() +from PyQt4.QtCore import * +from PyQt4.QtGui import *   -MERGE_PAGE = 0 -COMMIT_PAGE = 1 -RESULT_PAGE = 2 +SUMMARY_PAGE = 0 +MERGE_PAGE = 1 +COMMIT_PAGE = 2 +RESULT_PAGE = 3 + +MARGINS = (8, 0, 0, 0)    class MergeDialog(QWizard):   - def __init__(self, other, repo, parent): + def __init__(self, otherrev, repo, parent):   super(MergeDialog, self).__init__(parent)   f = self.windowFlags()   self.setWindowFlags(f & ~Qt.WindowContextHelpButtonHint)     self.repo = repo - self.other = str(other) - self.local = str(self.repo['.'].rev()) + self.otherrev = str(otherrev) + self.localrev = str(self.repo['.'].rev())     self.setWindowTitle(_('Merge - %s') % self.repo.displayname)   self.setWindowIcon(qtlib.geticon('hg-merge')) - self.setMinimumSize(600, 528) - self.setOption(QWizard.DisabledBackButtonOnLastPage, True)   self.setOption(QWizard.HelpButtonOnRight, False) - self.setDefaultProperty('QComboBox', 'currentText', 'editTextChanged()') + self.setOption(QWizard.IndependentPages, True)     # set pages - self.setPage(MERGE_PAGE, MergePage(self)) - self.setPage(COMMIT_PAGE, CommitPage(self)) - self.setPage(RESULT_PAGE, ResultPage(self)) + self.setPage(SUMMARY_PAGE, SummaryPage(repo, self)) + self.setPage(MERGE_PAGE, MergePage(repo, self)) + self.setPage(COMMIT_PAGE, CommitPage(repo, self)) + self.setPage(RESULT_PAGE, ResultPage(repo, self)) + self.currentIdChanged.connect(self.pageChanged)     repo.repositoryChanged.connect(self.repositoryChanged)   repo.configChanged.connect(self.configChanged) @@ -54,42 +55,27 @@
  def configChanged(self):   self.currentPage().configChanged()   + def pageChanged(self, id): + if id != -1: + self.currentPage().currentPage() +   def reject(self): - page = self.currentPage() - if hasattr(page, 'need_cleanup') and page.need_cleanup(): - main = _('Do you want to exit?') - text = _('To finish merging, you need to commit ' - 'the working directory.') - labels = ((QMessageBox.Yes, _('&Exit')), - (QMessageBox.No, _('Cancel'))) - if not qtlib.QuestionMsgBox(_('Confirm Exit'), main, text, - labels=labels, parent=self): - return - page.reject() - super(MergeDialog, self).reject() + if self.currentPage().canExit(): + super(MergeDialog, self).reject()   -MAIN_PANE = 0 -PERFORM_PANE = 1    class BasePage(QWizardPage): + def __init__(self, repo, parent): + super(BasePage, self).__init__(parent) + self.repo = repo   - def __init__(self, parent=None): - super(BasePage, self).__init__(parent) - self.nextEnabled = True - self.done = False + def validatePage(self): + 'user pressed NEXT button, can we proceed?' + return True   - def switch_pane(self, pane): - self.setup_buttons(pane) - self.layout().setCurrentIndex(pane) - if pane == MAIN_PANE: - self.ready() - elif pane == PERFORM_PANE: - self.cmd.core.clearOutput() - self.perform() - else: - raise 'unknown pane: %s' % pane - - ### Override Method ### + def isComplete(self): + 'should NEXT button be sensitive?' + return True     def repositoryChanged(self):   'repository has detected a change to changelog or parents' @@ -99,125 +85,39 @@
  'repository has detected a change to config files'   pass   - def reject(self): + def currentPage(self):   pass   + def canExit(self): + if len(self.repo.parents()) == 2: + main = _('Do you want to exit?') + text = _('To finish merging, you need to commit ' + 'the working directory.') + labels = ((QMessageBox.Yes, _('&Exit')), + (QMessageBox.No, _('Cancel'))) + if not qtlib.QuestionMsgBox(_('Confirm Exit'), main, text, + labels=labels, parent=self): + return False + return True + +class SummaryPage(BasePage): + + def __init__(self, repo, parent): + super(SummaryPage, self).__init__(repo, parent) + self.clean = None + self.th = None + + ### Override Methods ### +   def initializePage(self):   if self.layout():   return + self.setTitle(_('Preparation')) + self.setSubTitle(_('Verify merge targets and ensure your working ' + 'directory is clean.')) + self.setLayout(QVBoxLayout())   - stack = QStackedLayout() - self.setLayout(stack) - - def wrap(layout): - widget = QWidget() - widget.setLayout(layout) - return widget - - # main pane - fpane = self.get_pane() - num = stack.addWidget(wrap(fpane)) - assert num == MAIN_PANE - - # perform pane - ppane = QVBoxLayout() - ppane.addSpacing(4) - self.cmd = cmdui.Widget(True, True, self) - self.cmd.setShowOutput(True) - self.cmd.commandFinished.connect(self.command_finished) - self.cmd.commandCanceling.connect(self.command_canceling) - ppane.addWidget(self.cmd) - num = stack.addWidget(wrap(ppane)) - assert num == PERFORM_PANE - - def setVisible(self, visible): - super(BasePage, self).setVisible(visible) - if visible: - self.switch_pane(MAIN_PANE) - - def validatePage(self): - #When the user first clicks on the "Next" button ("Merge"/"Commit") - #After any validation via overloading validatePage(), - #we switch to the perform pane - if self.layout().currentIndex() == MAIN_PANE: - self.switch_pane(PERFORM_PANE) - return False - - #When the perform pane is done, it'll call this again - return self.can_continue() - - ### Method to be overridden ### - - def get_pane(self): - return QVBoxLayout() - - def get_perform_label(self): - return None - - def setup_buttons(self, pane): - if pane == MAIN_PANE: - label = self.get_perform_label() - if label: - self.wizard().setButtonText(QWizard.NextButton, label); - self.nextEnabled = True - else: - self.nextEnabled = False - self.wizard().setOption(QWizard.HaveHelpButton, False) - self.wizard().setOption(QWizard.HaveCustomButton1, False) - self.wizard().setOption(QWizard.NoCancelButton, False) - elif pane == PERFORM_PANE: - button = QPushButton(_('Cancel')) - self.wizard().setButton(QWizard.CustomButton1, button) - self.wizard().setOption(QWizard.HaveCustomButton1, True) - button.clicked.connect(self.cancel_clicked) - self.nextEnabled = False - self.wizard().setOption(QWizard.NoCancelButton, True) - else: - raise 'unknown pane: %s' % pane - - def ready(self): - pass - - def perform(self): - pass - - def cancel(self): - self.cmd.cancel() - - def can_continue(self): - return self.done - - def need_cleanup(self): - return False - - ### Signal Handlers ### - - def cancel_clicked(self): - self.cancel() - - def command_finished(self, ret): - pass - - def command_canceling(self): - pass - -MARGINS = (8, 0, 0, 0) - -class MergePage(BasePage): - - def __init__(self, parent=None): - super(MergePage, self).__init__(parent) - - self.clean = None - self.undo = False - self.th = None - - ### Override Methods ### - - def get_pane(self): - repo = self.wizard().repo - box = QVBoxLayout() - + repo = self.repo   contents = ('ishead',) + csinfo.PANEL_DEFAULT   style = csinfo.panelstyle(contents=contents)   def markup_func(widget, item, value): @@ -230,36 +130,31 @@
    ## merge target   other_sep = qtlib.LabeledSeparator(_('Merge from (other revision)')) - box.addWidget(other_sep) + self.layout().addWidget(other_sep)   try: - other_info = create(self.wizard().other) - other_info.setContentsMargins(5, 0, 0, 0) - box.addWidget(other_info) - self.other_info = other_info + otherCsInfo = create(self.wizard().otherrev)   except error.RepoLookupError:   qtlib.InfoMsgBox(_('Unable to merge'),   _('Merge revision not specified or not found'))   QTimer.singleShot(0, self.wizard().close) + self.layout().addWidget(otherCsInfo) + self.otherCsInfo = otherCsInfo     ## current revision - box.addSpacing(6)   local_sep = qtlib.LabeledSeparator(_('Merge to (working directory)')) - box.addWidget(local_sep) - local_info = create(self.wizard().local) - local_info.setContentsMargins(5, 0, 0, 0) - box.addWidget(local_info) - self.local_info = local_info + self.layout().addWidget(local_sep) + localCsInfo = create(self.wizard().localrev) + self.layout().addWidget(localCsInfo) + self.localCsInfo = localCsInfo     ## working directory status - box.addSpacing(6)   wd_sep = qtlib.LabeledSeparator(_('Working directory status')) - box.addWidget(wd_sep) + self.layout().addWidget(wd_sep)     self.groups = qtlib.WidgetGroups()     wdbox = QHBoxLayout() - wdbox.setContentsMargins(*MARGINS) - box.addLayout(wdbox) + self.layout().addLayout(wdbox)   self.wd_status = qtlib.StatusLabel()   self.wd_status.set_status(_('Checking...'))   wdbox.addWidget(self.wd_status) @@ -268,34 +163,28 @@
  wd_prog.setTextVisible(False)   self.groups.add(wd_prog, 'prog')   wdbox.addWidget(wd_prog, 1) - wd_detail = QLabel(_('<a href="view">View changes...</a>')) - wd_detail.linkActivated.connect(self.link_activated) - self.groups.add(wd_detail, 'detail') - wdbox.addWidget(wd_detail) - wdbox.addSpacing(4)     wd_merged = QLabel(_('The working directory is already <b>merged</b>. '   '<a href="skip"><b>Continue</b></a> or '   '<a href="discard"><b>discard</b></a> existing '   'merge.')) - wd_merged.setContentsMargins(*MARGINS) - wd_merged.linkActivated.connect(self.link_activated) + wd_merged.linkActivated.connect(self.onLinkActivated) + wd_merged.setWordWrap(True)   self.groups.add(wd_merged, 'merged') - box.addWidget(wd_merged) + self.layout().addWidget(wd_merged)     text = _('Before merging, 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.')   wd_text = QLabel(text) - wd_text.setContentsMargins(*MARGINS) - wd_text.linkActivated.connect(self.link_activated) + wd_text.setWordWrap(True) + wd_text.linkActivated.connect(self.onLinkActivated)   self.wd_text = wd_text   self.groups.add(wd_text, 'dirty') - box.addWidget(wd_text) + self.layout().addWidget(wd_text)     wdbox = QHBoxLayout() - wdbox.setContentsMargins(*MARGINS) - box.addLayout(wdbox) + self.layout().addLayout(wdbox)   wd_alt = QLabel(_('Or use:'))   self.groups.add(wd_alt, 'dirty')   wdbox.addWidget(wd_alt) @@ -305,25 +194,18 @@
  self.registerField('force', force_chk)   self.groups.add(force_chk, 'dirty')   wdbox.addWidget(force_chk) - wdbox.addStretch(0) - - box.addSpacing(6)     ### options   expander = qtlib.ExpanderLabel(_('Options'), False) - expander.expanded.connect(self.show_options) - box.addWidget(expander) + expander.expanded.connect(self.toggleShowOptions) + self.layout().addWidget(expander)   self.expander = expander   - optbox = QVBoxLayout() - optbox.setSpacing(6) - box.addLayout(optbox) -   ### discard option   discard_chk = QCheckBox(_('Discard all changes from merge target '   '(other) revision'))   self.registerField('discard', discard_chk) - optbox.addWidget(discard_chk) + self.layout().addWidget(discard_chk)   self.discard_chk = discard_chk     ## auto-resolve @@ -332,129 +214,95 @@
  autoresolve_chk.setChecked(   repo.ui.configbool('tortoisehg', 'autoresolve', False))   self.registerField('autoresolve', autoresolve_chk) - optbox.addWidget(autoresolve_chk) + self.layout().addWidget(autoresolve_chk)   self.autoresolve_chk = autoresolve_chk   - box.addStretch(0) - - return box - - def get_perform_label(self): - return _('&Merge') - - def ready(self): - self.done = False   self.setTitle(_('Merge another revision to the working directory'))   self.groups.set_visible(False, 'dirty')   self.groups.set_visible(False, 'merged') - self.groups.set_visible(False, 'detail') - self.show_options(self.expander.is_expanded()) - self.check_status() + self.toggleShowOptions(self.expander.is_expanded())   - if self.undo: - self.link_activated('discard:noconfirm') - self.undo = False + def isComplete(self): + 'should Next button be sensitive?' + return self.clean or self.field('force').toBool()     def validatePage(self): - #If we haven't already done the action, pop up a confirmation for - #dummy merge. - if not self.done and self.field('discard').toBool(): + 'validate that we can continue with the merge' + if self.field('discard').toBool():   labels = [(QMessageBox.Yes, _('&Discard')),   (QMessageBox.No, _('Cancel'))]   if not qtlib.QuestionMsgBox(_('Confirm Discard Changes'),   _('The changes from revision %s and all unmerged parents '   'will be discarded.\n\n'   'Are you sure this is what you want to do?') - % (self.other_info.get_data('revid')), + % (self.otherCsInfo.get_data('revid')),   labels=labels, parent=self):   return False + return super(SummaryPage, self).validatePage();   - return super(MergePage, self).validatePage(); + ## custom methods ##   - def perform(self): - self.setTitle(_('Merging...')) - self.setSubTitle(_('All conflicting files will be marked unresolved.')) - - if self.field('discard').toBool(): - # '.' is safer than self.localrev, in case the user has - # pulled a fast one on us and updated from the CLI - cmdline = ['--repository', self.wizard().repo.root, - 'debugsetparents', '.', self.wizard().other] - else: - cmdline = ['--repository', self.wizard().repo.root, 'merge'] - if self.field('force').toBool(): - cmdline.append('--force') - tool = self.field('autoresolve').toBool() and 'merge' or 'fail' - cmdline += ['--tool=internal:' + tool] - cmdline.append(self.wizard().other) - self.cmd.run(cmdline) - - def cancel(self): - main = _('Cancel merge and discard changes?') - # Does this restart "resolved" files too? - text = _('Discard local changes and restart merge?') - labels = ((QMessageBox.Yes, _('&Discard')), - (QMessageBox.No, _('Cancel'))) - if qtlib.QuestionMsgBox(_('Confirm Clean Up'), main, text, - labels=labels, parent=self): - o = self.cmd.outputLog - o.appendLog(_('Canceling merge...\n'), 'control') - o.appendLog(_('(Please close any running merge tools)\n'), 'control') - self.cmd.cancel() - - def isComplete(self): - if not self.nextEnabled: - return False - if self.clean: - return True - return self.field('force').toBool() - - ### Signal Handlers ### - - def command_finished(self, ret): - repo = self.wizard().repo - if ret in (0, 1): - repo.incrementBusyCount() - repo.decrementBusyCount() - self.done = True - self.wizard().next() - else: - qtlib.InfoMsgBox(_('Merge failed'), _('Returning to first page')) - self.link_activated('discard:noconfirm') - self.switch_pane(MAIN_PANE) + def toggleShowOptions(self, visible): + self.discard_chk.setShown(visible) + self.autoresolve_chk.setShown(visible)     def repositoryChanged(self):   'repository has detected a change to changelog or parents' - pctx = self.wizard().repo['.'] - self.local_info.update(pctx) - self.wizard().local = str(pctx.rev()) + pctx = self.repo['.'] + self.localCsInfo.update(pctx) + self.wizard().localrev = str(pctx.rev())   - def reject(self): - if self.th is not None and not self.th.isFinished(): + def canExit(self): + 'can merge tool be closed?' + if self.th is not None and self.th.isRunning():   self.th.cancel()   self.th.wait() + return True   - def show_options(self, visible): - self.discard_chk.setShown(visible) - self.autoresolve_chk.setShown(visible) + def currentPage(self): + self.refresh()   - def command_canceling(self): - self.wizard().button(QWizard.CustomButton1).setDisabled(True) + def refresh(self): + if self.th is None: + self.th = CheckThread(self.repo, self) + self.th.finished.connect(self.threadFinished) + if self.th.isRunning(): + return + self.groups.set_visible(True, 'prog') + self.th.start()   - def link_activated(self, cmd): - cmd = str(cmd) - repo = self.wizard().repo + def threadFinished(self): + self.groups.set_visible(False, 'prog') + if self.th.canceled: + return + dirty, parents = self.th.results + self.clean = not dirty + if dirty: + self.groups.set_visible(parents == 2, 'merged') + self.groups.set_visible(parents == 1, 'dirty') + self.wd_status.set_status(_('<b>Uncommitted local changes ' + 'are detected</b>'), 'thg-warning') + else: + self.groups.set_visible(False, 'dirty') + self.groups.set_visible(False, 'merged') + self.wd_status.set_status(_('Clean'), True) + self.completeChanged.emit() + + @pyqtSlot(QString) + def onLinkActivated(self, cmd): + cmd = hglib.fromunicode(cmd) + repo = self.repo   if cmd == 'commit':   dlg = commit.CommitDialog([], dict(root=repo.root), self)   dlg.finished.connect(dlg.deleteLater)   dlg.exec_() - self.check_status() + self.refresh()   elif cmd == 'shelve':   from tortoisehg.hgqt import shelve   dlg = shelve.ShelveDialog(repo, self.wizard())   dlg.finished.connect(dlg.deleteLater)   dlg.exec_() - self.check_status() + self.refresh()   elif cmd.startswith('discard'):   if cmd != 'discard:noconfirm':   labels = [(QMessageBox.Yes, _('&Discard')), @@ -465,111 +313,115 @@
  return   def finished(ret):   repo.decrementBusyCount() - if ret == 0: - self.check_status() + self.refresh()   cmdline = ['update', '--clean', '--repository', repo.root,   '--rev', '.']   self.runner = cmdui.Runner(True, self)   self.runner.commandFinished.connect(finished)   repo.incrementBusyCount()   self.runner.run(cmdline) - elif cmd.startswith('rename:'): - patch = cmd[7:] - name, ok = QInputDialog.getText(self, _('Rename Patch'), - _('Input a new patch name:'), text=patch) - if not ok or name == patch: - return - oldpatch = hglib.fromunicode(patch) - newpatch = hglib.fromunicode(name) - def finished(ret): - repo.decrementBusyCount() - if ret == 0: - text = _('The patch <b>%(old)s</b> is renamed to <b>' - '%(new)s</b>. <a href="rename:%(new)s"><b>' - 'Rename</b></a> again?') - self.wd_text.setText(text % dict(old=patch, new=name)) - self.runner = cmdui.Runner(True, self) - self.runner.commandFinished.connect(finished) - repo.incrementBusyCount() - self.runner.run(['qrename', '--repository', repo.root, - oldpatch, newpatch])   elif cmd == 'view':   dlg = status.StatusDialog([], {}, repo.root, self)   dlg.exec_() - self.check_status() + self.refresh()   elif cmd == 'skip': - self.done = True   self.wizard().next()   else: - raise 'unknown command: %s' % str(cmd) + raise 'unknown command: %s' % cmd   - ### Private Methods ###   - def check_status(self, callback=None): - repo = self.wizard().repo - class CheckThread(QThread): - def __init__(self, parent): - QThread.__init__(self, parent) - self.results = (False, 1) - self.canceled = False +class MergePage(BasePage): + # TODO: add a checkbox on this page to automatically continue   - def run(self): - unresolved = False - for root, path, status in thgrepo.recursiveMergeStatus(repo): - if self.canceled: - return - if status == 'u': - unresolved = True - break - wctx = repo[None] - dirty = bool(wctx.dirty()) or unresolved - self.results = (dirty, len(wctx.parents())) + def __init__(self, repo, parent): + super(MergePage, self).__init__(repo, parent) + self.mergecomplete = False   - def cancel(self): - self.canceled = True + self.setTitle(_('Merging...')) + self.setSubTitle(_('All conflicting files will be marked unresolved.')) + self.setLayout(QVBoxLayout())   - def completed(): - if self.th.canceled: - return - self.th.wait() - dirty, parents = self.th.results - self.clean = not dirty - self.groups.set_visible(False, 'prog') - self.groups.set_visible(dirty, 'detail') - if dirty: - self.groups.set_visible(parents == 2, 'merged') - self.groups.set_visible(parents == 1, 'dirty') - self.wd_status.set_status(_('<b>Uncommitted local changes ' - 'are detected</b>'), 'thg-warning') + self.cmd = cmdui.Widget(True, False, self) + self.cmd.commandFinished.connect(self.onCommandFinished) + self.cmd.setShowOutput(True) + self.layout().addWidget(self.cmd) + + self.reslabel = QLabel() + self.reslabel.linkActivated.connect(self.onLinkActivated) + self.layout().addWidget(self.reslabel) + + def currentPage(self): + if self.field('discard').toBool(): + # '.' is safer than self.localrev, in case the user has + # pulled a fast one on us and updated from the CLI + cmdline = ['--repository', self.repo.root, 'debugsetparents', + '.', self.wizard().otherrev] + else: + cmdline = ['--repository', self.repo.root, 'merge'] + if self.field('force').toBool(): + cmdline.append('--force') + tool = self.field('autoresolve').toBool() and 'merge' or 'fail' + cmdline += ['--tool=internal:' + tool] + cmdline.append(self.wizard().otherrev) + + if len(self.repo.parents()) == 1: + self.repo.incrementBusyCount() + self.cmd.core.clearOutput() + self.cmd.run(cmdline) + else: + self.mergecomplete = True + self.completeChanged.emit() + + def isComplete(self): + 'should Next button be sensitive?' + if not self.mergecomplete: + return False + count = 0 + for root, path, status in thgrepo.recursiveMergeStatus(self.repo): + if status == 'u': + count += 1 + if count: + if self.field('autoresolve').toBool(): + # if autoresolve is enabled, we know these were real conflicts + self.reslabel.setText(_('%d files have <b>merge conflicts</b> ' + 'that must be <a href="resolve">' + '<b>resolved</b></a>') % count)   else: - self.groups.set_visible(False, 'dirty') - self.groups.set_visible(False, 'merged') - self.wd_status.set_status(_('Clean'), True) + # else give a calmer indication of conflicts + self.reslabel.setText(_('%d files were modified on both ' + 'branches and must be <a href="resolve">' + '<b>resolved</b></a>') % count) + return False + else: + self.reslabel.setText(_('No merge conflicts, ready to commit')) + return True + + def onCommandFinished(self, ret): + self.repo.decrementBusyCount() + if ret in (0, 1): + self.mergecomplete = True + self.completeChanged.emit() + + @pyqtSlot(QString) + def onLinkActivated(self, cmd): + if cmd == 'resolve': + dlg = resolve.ResolveDialog(self.repo, self) + dlg.finished.connect(dlg.deleteLater) + dlg.exec_()   self.completeChanged.emit() - if callable(callback): - callback() - self.th = CheckThread(self) - self.th.finished.connect(completed) - self.th.start()      class CommitPage(BasePage):   - def __init__(self, parent=None): - super(CommitPage, self).__init__(parent) + def __init__(self, repo, parent): + super(CommitPage, self).__init__(repo, parent)   - ### Override Methods ### - - def get_pane(self): - repo = self.wizard().repo - box = QVBoxLayout() - - self.reslabel = QLabel() - self.reslabel.linkActivated.connect(self.link_activated) - box.addWidget(self.reslabel) + self.setTitle(_('Commit merge results')) + self.setLayout(QVBoxLayout()) + self.setCommitPage(True)     # csinfo - def label_func(widget, item): + def label_func(widget, item, ctx):   if item == 'rev':   return _('Revision:')   elif item == 'parents': @@ -614,28 +466,29 @@
  style = csinfo.panelstyle(contents=contents, margin=6)     # merged files - box.addSpacing(12)   rev_sep = qtlib.LabeledSeparator(_('Working Directory (merged)')) - box.addWidget(rev_sep) - rev_info = csinfo.create(repo, None, style, custom=custom, - withupdate=True) - page = self.wizard().page(MERGE_PAGE) - rev_info.linkActivated.connect(page.link_activated) - box.addWidget(rev_info) + self.layout().addWidget(rev_sep) + mergeCsInfo = csinfo.create(repo, None, style, custom=custom, + withupdate=True) + mergeCsInfo.linkActivated.connect(self.onLinkActivated) + self.layout().addWidget(mergeCsInfo)     # commit message area   msg_sep = qtlib.LabeledSeparator(_('Commit message')) - box.addWidget(msg_sep) - msg_text = commit.MessageEntry(self) - msg_text.installEventFilter(qscilib.KeyPressInterceptor(self)) - msg_text.refresh(repo) - msg_text.loadSettings(QSettings(), 'merge/message') - engmsg = repo.ui.configbool('tortoisehg', 'engmsg', False) - msgset = keep._('Merge ') - msg_text.setText(engmsg and msgset['id'] or msgset['str']) - msg_text.textChanged.connect(self.completeChanged) - self.msg_text = msg_text - box.addWidget(msg_text) + self.layout().addWidget(msg_sep) + msgEntry = commit.MessageEntry(self) + msgEntry.installEventFilter(qscilib.KeyPressInterceptor(self)) + msgEntry.refresh(repo) + msgEntry.loadSettings(QSettings(), 'merge/message') + + msgEntry.textChanged.connect(self.completeChanged) + self.layout().addWidget(msgEntry) + self.msgEntry = msgEntry + + self.cmd = cmdui.Widget(True, False, self) + self.cmd.commandFinished.connect(self.onCommandFinished) + self.cmd.setShowOutput(False) + self.layout().addWidget(self.cmd)     def tryperform():   if self.isComplete(): @@ -644,121 +497,90 @@
  actionEnter.setShortcuts([Qt.CTRL+Qt.Key_Return, Qt.CTRL+Qt.Key_Enter])   actionEnter.triggered.connect(tryperform)   self.addAction(actionEnter) - return box   - def link_activated(self, cmd): - if cmd == 'resolve': - dlg = resolve.ResolveDialog(self.wizard().repo, self) - dlg.finished.connect(dlg.deleteLater) + def currentPage(self): + # TODO: add other branch name, when appropriate + engmsg = self.repo.ui.configbool('tortoisehg', 'engmsg', False) + msgset = i18n.keepgettext()._('Merge ') + self.msgEntry.setText(engmsg and msgset['id'] or msgset['str']) + self.msgEntry.moveCursorToEnd() + + @pyqtSlot(QString) + def onLinkActivated(self, cmd): + if cmd == 'view': + dlg = status.StatusDialog([], {}, self.repo.root, self)   dlg.exec_() - self.completeChanged.emit() + self.refresh()   - def get_perform_label(self): - return _('&Commit') + def isComplete(self): + return len(self.repo.parents()) == 2 and len(self.msgEntry.text()) > 0   - def setup_buttons(self, pane): - super(CommitPage, self).setup_buttons(pane) + def validatePage(self): + if len(self.repo.parents()) == 1: + # commit succeeded, repositoryChanged() called wizard().next() + return True + if self.cmd.core.running(): + return False   - if pane == MAIN_PANE: - undo = QPushButton(_('Undo')) - undo.clicked.connect(self.wizard().back) - self.wizard().setButton(QWizard.HelpButton, undo) - self.wizard().setOption(QWizard.HaveHelpButton, True) - elif pane == PERFORM_PANE: - self.wizard().setOption(QWizard.HaveHelpButton, False) - else: - raise 'unknown pane: %s' % pane - - def ready(self): - self.setTitle(_('Commit merged files')) - self.msg_text.moveCursorToEnd() - - def perform(self):   self.setTitle(_('Committing...'))   self.setSubTitle(_('Please wait while committing merged files.'))   - # merges must be committed without specifying file list - message = hglib.fromunicode(self.msg_text.text()) + message = hglib.fromunicode(self.msgEntry.text())   cmdline = ['commit', '--verbose', '--message', message, - '--repository', self.wizard().repo.root] - self.wizard().repo.incrementBusyCount() + '--repository', self.repo.root] + self.repo.incrementBusyCount() + self.cmd.setShowOutput(True)   self.cmd.run(cmdline) - - def isComplete(self): - if not self.nextEnabled: - return False - repo = self.wizard().repo - for root, path, status in thgrepo.recursiveMergeStatus(repo): - if status == 'u': - self.reslabel.setText(_('There were <b>merge conflicts</b> ' - 'that must be <a href="resolve">' - '<b>resolved</b></a>')) - return False - else: - self.reslabel.setText(_('No merge conflicts, ready to commit')) - return len(self.msg_text.text()) > 0 - - def need_cleanup(self): - return len(self.wizard().repo.parents()) == 2 + self.msgEntry.saveSettings(QSettings(), 'merge/message') + return False     def repositoryChanged(self):   'repository has detected a change to changelog or parents' - if self.done: - return - if len(self.wizard().repo.parents()) == 1: - self.wizard().restart() + if len(self.repo.parents()) == 1: + self.wizard().next()   - ### Private Method ### + def onCommandFinished(self, ret): + self.repo.decrementBusyCount() + self.completeChanged.emit()   - def undo(self): - page = self.wizard().page(MERGE_PAGE) - page.undo = True +class ResultPage(BasePage): + # TODO: Add a checkbox on this page to make it optional   - ### Signal Handlers ### + def __init__(self, repo, parent): + super(ResultPage, self).__init__(repo, parent) + self.setTitle(_('Finished')) + self.setFinalPage(True)   - def command_finished(self, ret): - if ret == 0: - self.done = True - self.wizard().repo.decrementBusyCount() - self.msg_text.saveSettings(QSettings(), 'merge/message') - self.wizard().next() - else: - self.wizard().repo.decrementBusyCount() + self.setLayout(QVBoxLayout()) + merge_sep = qtlib.LabeledSeparator(_('Merge changeset')) + self.layout().addWidget(merge_sep) + merge_info = csinfo.create(self.repo, 'tip', withupdate=True) + self.layout().addWidget(merge_info) + self.layout().addStretch(1)   - def command_canceling(self): - page = self.wizard().page(MERGE_PAGE) - page.undo = True   -class ResultPage(QWizardPage): +class CheckThread(QThread): + def __init__(self, repo, parent): + QThread.__init__(self, parent) + self.repo = hg.repository(repo.ui, repo.root) + self.results = (False, 1) + self.canceled = False   - def __init__(self, parent=None): - super(ResultPage, self).__init__(parent) + def run(self): + self.repo.dirstate.invalidate() + unresolved = False + for root, path, status in thgrepo.recursiveMergeStatus(self.repo): + if self.canceled: + return + if status == 'u': + unresolved = True + break + wctx = self.repo[None] + dirty = bool(wctx.dirty()) or unresolved + self.results = (dirty, len(wctx.parents()))   - self.setTitle(_('Finished')) - - ### Override Method ### - - def reject(self): - pass - - def repositoryChanged(self): - 'repository has detected a change to changelog or parents' - pass - - def initializePage(self): - box = QVBoxLayout() - self.setLayout(box) - - # merge changeset - merge_sep = qtlib.LabeledSeparator(_('Merge changeset')) - box.addWidget(merge_sep) - merge_info = csinfo.create(self.wizard().repo, 'tip', withupdate=True) - box.addWidget(merge_info) - box.addStretch(0) - - self.wizard().setOption(QWizard.HaveHelpButton, False) - self.wizard().setOption(QWizard.NoCancelButton, True) - self.wizard().setOption(QWizard.HaveCustomButton1, False) + def cancel(self): + self.canceled = True    def run(ui, *pats, **opts):   from tortoisehg.util import paths
Show Entire File tortoisehg/​hgqt/​mq.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File tortoisehg/​hgqt/​pbranch.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
 
10
11
12
 
13
14
15
16
 
17
18
19
 
28
29
30
 
 
 
 
 
 
31
32
33
 
79
80
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
83
84
 
10
11
12
13
14
15
16
 
17
18
19
20
 
29
30
31
32
33
34
35
36
37
38
39
40
 
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
@@ -10,10 +10,11 @@
 import atexit  import shutil  import stat +import subprocess  import tempfile  import re   -from mercurial import extensions +from mercurial import extensions, util    from tortoisehg.util import hglib, paths, wconfig  from hgext.color import _styles @@ -28,6 +29,12 @@
  (QT_VERSION_STR, PYQT_VERSION_STR))   sys.exit()   +try: + import win32con + openflags = win32con.CREATE_NO_WINDOW +except ImportError: + openflags = 0 +  tmproot = None  def gettempdir():   global tmproot @@ -79,6 +86,64 @@
    return fn, wconfig.readfile(fn)   +def editfiles(repo, files, lineno=None, search=None, parent=None): + files = [util.shellquote(util.localpath(f)) for f in files] + editor = repo.ui.config('tortoisehg', 'editor') + assert len(files) == 1 or lineno == None + if editor: + try: + regexp = re.compile('\[([^\]]*)\]') + expanded = [] + pos = 0 + for m in regexp.finditer(editor): + expanded.append(editor[pos:m.start()-1]) + phrase = editor[m.start()+1:m.end()-1] + pos=m.end()+1 + if '$LINENUM' in phrase: + if lineno is None: + # throw away phrase + continue + phrase = phrase.replace('$LINENUM', str(lineno)) + elif '$SEARCH' in phrase: + if search is None: + # throw away phrase + continue + phrase = phrase.replace('$SEARCH', search) + if '$FILE' in phrase: + phrase = phrase.replace('$FILE', files[0]) + files = [] + expanded.append(phrase) + expanded.append(editor[pos:]) + cmdline = ' '.join(expanded + files) + except ValueError, e: + # '[' or ']' not found + cmdline = ' '.join([editor] + files) + except TypeError, e: + # variable expansion failed + cmdline = ' '.join([editor] + files) + else: + editor = os.environ.get('HGEDITOR') or repo.ui.config('ui', 'editor') \ + or os.environ.get('EDITOR', 'vi') + cmdline = ' '.join([editor] + files) + if os.path.basename(editor) in ('vi', 'vim', 'hgeditor'): + res = QMessageBox.critical(parent, + _('No visual editor configured'), + _('Please configure a visual editor.')) + from tortoisehg.hgqt.settings import SettingsDialog + dlg = SettingsDialog(False, focus='tortoisehg.editor') + dlg.exec_() + return + + cmdline = util.quotecommand(cmdline) + try: + subprocess.Popen(cmdline, shell=True, creationflags=openflags, + stderr=None, stdout=None, stdin=None, cwd=repo.root) + except (OSError, EnvironmentError), e: + QMessageBox.warning(parent, + _('Editor launch failure'), + _('%s : %s') % (cmd, str(e))) + return False +  # _styles maps from ui labels to effects  # _effects maps an effect to font style properties. We define a limited  # set of _effects, since we convert color effect names to font style
 
181
182
183
184
185
186
187
188
 
189
190
191
 
181
182
183
 
 
 
 
 
184
185
186
187
@@ -181,11 +181,7 @@
  return   root = self.selitem.internalPointer().rootpath()   d = CloneDialog(args=[root, root + '-clone'], parent=self) - def cmdfinished(res): - if res == 0: - dest = d.getDest() - self.workbench.openRepo(dest) - d.cmdfinished.connect(cmdfinished) + d.clonedRepository.connect(self.workbench.showRepo)   d.show()     def explore(self):
Show Entire File tortoisehg/​hgqt/​repowidget.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File tortoisehg/​hgqt/​resolve.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File tortoisehg/​hgqt/​revdetails.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File tortoisehg/​hgqt/​revpanel.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File tortoisehg/​hgqt/​run.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File tortoisehg/​hgqt/​rupdate.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File tortoisehg/​hgqt/​settings.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File tortoisehg/​hgqt/​status.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File tortoisehg/​hgqt/​sync.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File tortoisehg/​hgqt/​update.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File tortoisehg/​hgqt/​visdiff.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File tortoisehg/​hgqt/​wctxactions.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File tortoisehg/​hgqt/​workbench.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File tortoisehg/​util/​hglib.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes