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 5df8bcb46c23

Parents c969f014c035

Parents de4a2cbd7d05

by Steve Borho

Changes to 18 files · Browse files at 5df8bcb46c23 Showing diff from parent c969f014c035 de4a2cbd7d05 Diff from another changeset...

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
 
398
399
400
401
 
402
403
404
 
9
10
11
 
12
13
14
15
 
398
399
400
 
401
402
403
404
@@ -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 @@ -398,7 +398,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
 
141
142
143
144
145
 
 
146
147
148
 
15
16
17
 
18
19
20
21
 
141
142
143
 
 
144
145
146
147
148
@@ -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 * @@ -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
 
18
19
20
21
 
22
23
24
25
26
 
164
165
166
167
168
 
169
170
171
172
 
173
174
175
 
177
178
179
180
181
 
182
183
184
 
18
19
20
 
21
22
 
23
24
25
 
163
164
165
 
 
166
167
168
169
 
170
171
172
173
 
175
176
177
 
 
178
179
180
181
@@ -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 * @@ -164,12 +163,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 +175,7 @@
  return   model = self.model()   repo = model.repo - path = repo.wjoin(filename) - wctxactions.edit(self, repo.ui, repo, [path]) + qtlib.editfiles(repo, [path], parent=self)     def revertfile(self):   filename = self.currentFile()
 
27
28
29
30
 
31
32
33
 
260
261
262
 
 
 
 
263
264
265
 
397
398
399
400
 
401
402
403
 
27
28
29
 
30
31
32
33
 
260
261
262
263
264
265
266
267
268
269
 
401
402
403
 
404
405
406
407
@@ -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 * @@ -260,6 +260,10 @@
  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 @@ -397,7 +401,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):
 
632
633
634
635
636
637
638
 
642
643
644
645
646
 
647
648
649
650
 
651
652
653
 
632
633
634
 
635
636
637
 
641
642
643
 
 
644
645
646
647
 
648
649
650
651
@@ -632,7 +632,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: @@ -642,12 +641,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
 
 
 
 
26
27
28
 
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
 
376
377
378
379
380
 
 
 
 
 
 
 
 
381
382
383
384
385
386
 
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
26
27
28
29
30
31
32
 
43
44
45
 
46
47
48
49
 
 
 
 
 
 
50
 
51
52
53
54
 
78
79
80
 
81
82
83
84
 
90
91
92
 
93
94
95
96
 
101
102
103
 
104
105
106
107
108
109
 
 
 
110
111
112
 
144
145
146
 
147
 
148
149
150
151
152
 
 
 
 
 
 
 
 
153
154
155
156
157
 
169
170
171
 
 
 
 
 
 
 
 
 
 
 
 
172
173
174
 
229
230
231
 
 
232
233
234
235
236
 
237
238
239
240
241
 
 
242
243
244
245
 
351
352
353
 
 
354
355
356
357
358
359
360
361
362
 
 
363
364
365
 
374
375
376
 
 
 
 
 
 
377
378
379
380
381
382
383
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
385
386
 
393
394
395
 
396
397
398
399
@@ -18,11 +18,15 @@
 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   +# TODO +# Communicate status (MARC) from model to HgFileView +# Allow manifest mode in HgFileView even when no diffs are available +  class ManifestDialog(QMainWindow):   """   Qt4 dialog to display all files of a repo at a given revision @@ -39,19 +43,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 +78,7 @@
    def setSearchPattern(self, text):   """Set search pattern [unicode]""" - self._searchbar.setPattern(text) + self._fileview.searchbar.setPattern(text)     @pyqtSlot(unicode, dict)   def _openSearchWidget(self, pattern, opts): @@ -93,7 +90,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 +101,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 +144,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 +169,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 +229,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: @@ -376,11 +351,15 @@
  self._rev = rev   self._setupmodel()   self.setPath(path) - if self.path in self._repo[rev]: - self._fileview.setSource(path, rev, line) + ctx = self._repo[rev] + if self.path in ctx: + self._fileview.setContext(ctx) + self._fileview.displayFile(path, rev) + if line: + self._fileview.showLine(int(line) - 1) + else: + self._fileview.clearDisplay()   if revchanged: - # annotate working copy is not supported - self._action_annotate_mode.setEnabled(rev is not None)   self.revChanged.emit(rev)     @property @@ -395,38 +374,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._rev, 'C') # TODO     @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 +393,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):
 
20
21
22
 
23
24
25
 
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
 
183
184
185
186
187
188
189
190
191
192
 
 
 
 
 
 
 
193
194
195
 
20
21
22
23
24
25
26
 
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
 
189
190
191
 
 
 
 
 
 
 
192
193
194
195
196
197
198
199
200
201
@@ -20,6 +20,7 @@
 from tortoisehg.hgqt.i18n import _  from tortoisehg.hgqt import qtlib, cmdui, rejects, commit, qscilib  from tortoisehg.hgqt import qqueue, fileview +from tortoisehg.hgqt.qtlib import geticon    # TODO  # keep original file name in file list item @@ -81,36 +82,41 @@
  qtbarhbox.setSpacing(5)   layout.addLayout(qtbarhbox, 0)   qtbarhbox.setContentsMargins(0, 0, 0, 0) - self.qpushAllBtn = tb = QToolButton() - tb.setIcon(qtlib.geticon('hg-qpush-all')) - tb.setToolTip(_('Apply all patches')) - self.qpushBtn = tb = QToolButton() - tb.setIcon(qtlib.geticon('hg-qpush')) - tb.setToolTip(_('Apply one patch')) - self.setGuardsBtn = tb = QToolButton() - tb.setIcon(qtlib.geticon('hg-qguard')) - tb.setToolTip(_('Configure guards for selected patch')) - self.qpushMoveBtn = tb = QToolButton() - tb.setIcon(qtlib.geticon('thg-qreorder')) - tb.setToolTip(_('Apply selected patch next (change queue order)')) - self.qdeleteBtn = tb = QToolButton() - tb.setIcon(qtlib.geticon('hg-qdelete')) - tb.setToolTip(_('Delete selected patches')) - self.qpopBtn = tb = QToolButton() - tb.setIcon(qtlib.geticon('hg-qpop')) - tb.setToolTip(_('Unapply one patch')) - self.qpopAllBtn = tb = QToolButton() - tb.setIcon(qtlib.geticon('hg-qpop-all')) - tb.setToolTip(_('Unapply all patches')) - qtbarhbox.addWidget(self.qpushAllBtn) - qtbarhbox.addWidget(self.qpushBtn) - qtbarhbox.addStretch(1) - qtbarhbox.addWidget(self.setGuardsBtn) - qtbarhbox.addWidget(self.qpushMoveBtn) - qtbarhbox.addWidget(self.qdeleteBtn) - qtbarhbox.addStretch(1) - qtbarhbox.addWidget(self.qpopBtn) - qtbarhbox.addWidget(self.qpopAllBtn) + self.qpushAllBtn = a = QAction( + geticon('hg-qpush-all'), _('Push all'), self) + a.setToolTip(_('Apply all patches')) + self.qpushBtn = a = QAction( + geticon('hg-qpush'), _('Push'), self) + a.setToolTip(_('Apply one patch')) + self.setGuardsBtn = a = QAction( + geticon('hg-qguard'), _('Guards'), self) + a.setToolTip(_('Configure guards for selected patch')) + self.qpushMoveBtn = a = QAction( + geticon('hg-qreorder'), _('Push selected'), self) + a.setToolTip(_('Apply selected patch next (change queue order)')) + self.qdeleteBtn = a = QAction( + geticon('hg-qdelete'), _('Delete'), self) + a.setToolTip(_('Delete selected patches')) + self.qpopBtn = a = QAction( + geticon('hg-qpop'), _('Pop'), self) + a.setToolTip(_('Unapply one patch')) + self.qpopAllBtn = a = QAction( + geticon('hg-qpop-all'), _('Pop all'), self) + a.setToolTip(_('Unapply all patches')) + tbar = QToolBar(_('Patch Queue Actions Toolbar')) + tbar.setIconSize(QSize(18, 18)) + qtbarhbox.addWidget(tbar) + tbar.addAction(self.qpushBtn) + tbar.addAction(self.qpushAllBtn) + tbar.addSeparator() + tbar.addAction(self.qpopBtn) + tbar.addAction(self.qpopAllBtn) + tbar.addSeparator() + tbar.addAction(self.qpushMoveBtn) + tbar.addSeparator() + tbar.addAction(self.qdeleteBtn) + tbar.addSeparator() + tbar.addAction(self.setGuardsBtn)     self.queueListWidget = QListWidget(self)   layout.addWidget(self.queueListWidget, 1) @@ -183,13 +189,13 @@
  self.queueListWidget.currentRowChanged.connect(self.onPatchSelected)   self.queueListWidget.itemActivated.connect(self.onGotoPatch)   self.queueListWidget.itemChanged.connect(self.onRenamePatch) - self.qpushAllBtn.clicked.connect(self.onPushAll) - self.qpushBtn.clicked.connect(self.onPush) - self.qpushMoveBtn.clicked.connect(self.onPushMove) - self.qpopAllBtn.clicked.connect(self.onPopAll) - self.qpopBtn.clicked.connect(self.onPop) - self.setGuardsBtn.clicked.connect(self.onGuardConfigure) - self.qdeleteBtn.clicked.connect(self.onDelete) + self.qpushAllBtn.triggered.connect(self.onPushAll) + self.qpushBtn.triggered.connect(self.onPush) + self.qpushMoveBtn.triggered.connect(self.onPushMove) + self.qpopAllBtn.triggered.connect(self.onPopAll) + self.qpopBtn.triggered.connect(self.onPop) + self.setGuardsBtn.triggered.connect(self.onGuardConfigure) + self.qdeleteBtn.triggered.connect(self.onDelete)   self.newCheckBox.toggled.connect(self.onNewModeToggled)   self.qnewOrRefreshBtn.clicked.connect(self.onQNewOrQRefresh)  
 
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
 
27
28
29
30
 
31
32
33
 
261
262
263
264
 
265
266
267
 
268
269
270
 
27
28
29
 
30
31
32
33
 
261
262
263
 
264
265
266
 
267
268
269
270
@@ -27,7 +27,7 @@
 from tortoisehg.hgqt.repoview import HgRepoView  from tortoisehg.hgqt.revdetails import RevDetailsWidget  from tortoisehg.hgqt.commit import CommitWidget -from tortoisehg.hgqt.manifestdialog import ManifestTaskWidget +from tortoisehg.hgqt.manifestdialog import ManifestWidget  from tortoisehg.hgqt.sync import SyncWidget  from tortoisehg.hgqt.grep import SearchWidget  from tortoisehg.hgqt.pbranch import PatchBranchWidget @@ -261,10 +261,10 @@
  rev = None   else:   rev = self.rev - w = ManifestTaskWidget(self.repo, rev, self) + w = ManifestWidget(self.repo, rev, self)   w.loadSettings(QSettings(), 'workbench')   w.revChanged.connect(self.repoview.goto) - w.revisionHint.connect(self.showMessage) + w.showMessage.connect(self.showMessage)   w.grepRequested.connect(self.grep)   return w  
 
14
15
16
17
 
18
19
20
 
192
193
194
195
 
196
197
198
 
14
15
16
 
17
18
19
20
 
192
193
194
 
195
196
197
198
@@ -14,7 +14,7 @@
   from tortoisehg.util import hglib  from tortoisehg.hgqt.i18n import _ -from tortoisehg.hgqt import qtlib, cmdui, wctxactions, visdiff, thgrepo +from tortoisehg.hgqt import qtlib, cmdui, visdiff, thgrepo    MARGINS = (8, 0, 0, 0)   @@ -192,7 +192,7 @@
  paths = self.getSelectedPaths(self.rtree)   if paths:   abspaths = [os.path.join(r,w) for r,w in paths] - wctxactions.edit(self, self.repo.ui, self.repo, abspaths) + qtlib.editfiles(self.repo, abspaths, parent=self)     def getVdiffFiles(self, tree):   paths = self.getSelectedPaths(self.rtree)
 
21
22
23
24
 
25
26
27
 
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
 
87
88
89
90
 
91
92
93
 
179
180
181
182
 
183
184
185
 
203
204
205
206
 
207
208
209
 
228
229
230
231
 
 
 
 
 
 
 
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
 
302
303
304
305
 
306
307
308
 
364
365
366
 
367
368
369
 
415
416
417
 
 
 
 
 
418
419
 
420
421
422
 
21
22
23
 
24
25
26
27
 
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
 
94
95
96
 
97
98
99
100
 
186
187
188
 
189
190
191
192
 
210
211
212
 
213
214
215
216
 
235
236
237
 
238
239
240
241
242
243
244
245
246
247
248
249
250
 
 
 
 
 
 
251
252
253
 
309
310
311
 
312
313
314
315
 
371
372
373
374
375
376
377
 
423
424
425
426
427
428
429
430
431
 
432
433
434
435
@@ -21,7 +21,7 @@
 from PyQt4.QtCore import *  from PyQt4.QtGui import *   -import mercurial.ui as _ui +import mercurial.ui as uimod  from mercurial import hg, util, fancyopts, cmdutil, extensions, error    from tortoisehg.hgqt.i18n import agettext as _ @@ -35,37 +35,44 @@
 except ImportError:   config_nofork = None   -try: - import win32con - openflags = win32con.CREATE_NO_WINDOW -except ImportError: - openflags = 0 -  nonrepo_commands = '''userconfig shellconfig clone debugcomplete init  about help version thgstatus serve rejects log'''    def dispatch(args):   """run the command specified in args"""   try: - u = _ui.ui() + u = uimod.ui()   if '--traceback' in args:   u.setconfig('ui', 'traceback', 'on')   if '--debugger' in args:   pdb.set_trace()   return _runcatch(u, args) + except error.ParseError, e: + from tortoisehg.hgqt.bugreport import ExceptionMsgBox + opts = {} + opts['cmd'] = ' '.join(sys.argv[1:]) + opts['values'] = e + opts['error'] = traceback.format_exc() + opts['nofork'] = True + errstring = _('Error string "%(arg0)s" at %(arg1)s<br>Please ' + '<a href="#edit:%(arg1)s">edit</a> your config') + main = QApplication(sys.argv) + dlg = ExceptionMsgBox(hglib.tounicode(str(e)), errstring, opts, + parent=None) + dlg.exec_() + except Exception, e: + # generic errors before the QApplication is started + if '--debugger' in args: + pdb.post_mortem(sys.exc_info()[2]) + opts = {} + opts['cmd'] = ' '.join(sys.argv[1:]) + opts['error'] = traceback.format_exc() + opts['nofork'] = True + return qtrun(bugrun, u, **opts)   except SystemExit:   pass   except KeyboardInterrupt:   print _('\nCaught keyboard interrupt, aborting.\n') - except: - if '--debugger' in args: - pdb.post_mortem(sys.exc_info()[2]) - error = traceback.format_exc() - opts = {} - opts['cmd'] = ' '.join(sys.argv[1:]) - opts['error'] = error - opts['nofork'] = True - return qtrun(bugrun, u, **opts)    origwdir = os.getcwd()  def portable_fork(ui, opts): @@ -87,7 +94,7 @@
  cmdline = subprocess.list2cmdline(args)   os.chdir(origwdir)   subprocess.Popen(cmdline, - creationflags=openflags, + creationflags=qtlib.openflags,   shell=True)   sys.exit(0)   @@ -179,7 +186,7 @@
  try:   args = fancyopts.fancyopts(args, globalopts, options)   except fancyopts.getopt.GetoptError, inst: - raise error.ParseError(None, inst) + raise error.CommandError(None, inst)     if args:   alias, args = args[0], args[1:] @@ -203,7 +210,7 @@
  try:   args = fancyopts.fancyopts(args, c, cmdoptions)   except fancyopts.getopt.GetoptError, inst: - raise error.ParseError(cmd, inst) + raise error.CommandError(cmd, inst)     # separate global options back out   for o in globalopts: @@ -228,19 +235,19 @@
  return runcommand(ui, args)   finally:   ui.flush() - except error.ParseError, inst: + except error.AmbiguousCommand, inst: + ui.status(_("thg: command '%s' is ambiguous:\n %s\n") % + (inst.args[0], " ".join(inst.args[1]))) + except error.UnknownCommand, inst: + ui.status(_("thg: unknown command '%s'\n") % inst.args[0]) + help_(ui, 'shortlist') + except error.CommandError, inst:   if inst.args[0]:   ui.status(_("thg %s: %s\n") % (inst.args[0], inst.args[1]))   help_(ui, inst.args[0])   else:   ui.status(_("thg: %s\n") % inst.args[1])   help_(ui, 'shortlist') - except error.AmbiguousCommand, inst: - ui.status(_("thg: command '%s' is ambiguous:\n %s\n") % - (inst.args[0], " ".join(inst.args[1]))) - except error.UnknownCommand, inst: - ui.status(_("thg: unknown command '%s'\n") % inst.args[0]) - help_(ui, 'shortlist')   except error.RepoError, inst:   ui.status(_("abort: %s!\n") % inst)   @@ -302,7 +309,7 @@
  try:   return cmdfunc()   except error.SignatureError: - raise error.ParseError(cmd, _("invalid arguments")) + raise error.CommandError(cmd, _("invalid arguments"))     if options['profile']:   format = ui.config('profiling', 'format', default='text') @@ -364,6 +371,7 @@
  error.RepoLookupError: _('Try refreshing your repository.'),   error.ParseError: _('Error string "%(arg0)s" at %(arg1)s<br>Please '   '<a href="#edit:%(arg1)s">edit</a> your config'), + error.Abort: _('Operation aborted:<br><br>%(arg0)s.'),   }     def __init__(self): @@ -415,8 +423,13 @@
  etype, evalue = self.errors[0][:2]   if len(self.errors) == 1 and etype in self._recoverableexc:   opts['values'] = evalue + errstr = self._recoverableexc[etype] + if etype == error.Abort and evalue.hint: + errstr = u''.join([errstr, u'<br><b>', _('hint:'), + u'</b> %(arg1)s']) + opts['values'] = [str(evalue), evalue.hint]   dlg = ExceptionMsgBox(hglib.tounicode(str(evalue)), - self._recoverableexc[etype], opts, + errstr, opts,   parent=self._mainapp.activeWindow())   elif etype is KeyboardInterrupt:   if qtlib.QuestionMsgBox(_('Keyboard interrupt'),
 
303
304
305
306
307
308
309
310
311
 
303
304
305
 
 
 
306
307
308
@@ -303,9 +303,6 @@
  _('The maximum size file (in KB) that TortoiseHg will '   'show changes for in the changelog, status, and commit windows. '   'A value of zero implies no limit. Default: 1024 (1MB)')), - _fi(_('Capture stderr'), 'tortoisehg.stderrcapt', genBoolCombo, - _('Redirect stderr to a buffer which is parsed at the end of ' - 'the process for runtime errors. Default: True')),   _fi(_('Fork GUI'), 'tortoisehg.guifork', genBoolCombo,   _('When running from the command line, fork a background '   'process to run graphical dialogs. Default: True')),
 
121
122
123
 
124
125
126
 
152
153
154
155
156
157
158
159
 
 
160
161
162
 
207
208
209
 
 
 
 
 
 
210
211
212
 
287
288
289
290
 
 
291
292
 
293
294
295
 
298
299
300
 
 
 
 
 
 
 
 
 
 
301
302
303
 
328
329
330
 
 
 
 
 
 
 
 
331
332
333
 
334
335
336
 
396
397
398
399
 
400
401
402
 
416
417
418
419
420
421
422
423
424
425
426
427
 
428
429
430
 
458
459
460
461
462
463
464
 
 
465
466
467
 
121
122
123
124
125
126
127
 
153
154
155
 
156
157
158
159
160
161
162
163
164
 
209
210
211
212
213
214
215
216
217
218
219
220
 
295
296
297
 
298
299
300
301
302
303
304
305
 
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
 
348
349
350
351
352
353
354
355
356
357
358
359
360
 
361
362
363
364
 
424
425
426
 
427
428
429
430
 
444
445
446
 
 
 
 
 
 
 
 
 
447
448
449
450
 
478
479
480
 
 
 
 
481
482
483
484
485
@@ -121,6 +121,7 @@
  self.filelistToolbar.addWidget(self.statusfilter)   self.filelistToolbar.addSeparator()   self.filelistToolbar.addWidget(self.refreshBtn) + self.actions = wctxactions.WctxActions(self.repo, self)   tv = WctxFileTree(self.repo)   vbox.addLayout(hbox)   vbox.addWidget(tv) @@ -152,11 +153,12 @@
  hcbox.addStretch(1)   hcbox.addWidget(self.countlbl)   - tv.menuAction.connect(self.refreshWctx)   tv.setItemsExpandable(False)   tv.setRootIsDecorated(False)   tv.sortByColumn(COL_STATUS, Qt.AscendingOrder)   tv.clicked.connect(self.onRowClicked) + tv.doubleClicked.connect(self.onRowDoubleClicked) + tv.menuRequest.connect(self.onMenuRequest)   le.textEdited.connect(self.setFilter)     def statusTypeTrigger(status): @@ -207,6 +209,12 @@
  self.fileview.saveSettings(qs, prefix+'/fileview')   qs.setValue(prefix+'/state', self.split.saveState())   + @pyqtSlot(QPoint, object) + def onMenuRequest(self, point, selected): + menu = self.actions.makeMenu(selected) + if menu.exec_(point): + self.refreshWctx() +   def refreshWctx(self):   if self.refthread:   return @@ -287,9 +295,11 @@
  curidx = tm.index(i, 0)   else:   selmodel.select(curidx, flags) - selmodel.currentChanged.connect(self.currentChanged) + selmodel.currentChanged.connect(self.onCurrentChange) + selmodel.selectionChanged.connect(self.onSelectionChange)   if curidx and curidx.isValid():   selmodel.setCurrentIndex(curidx, QItemSelectionModel.Current) + self.onSelectionChange(None, None)     # Disabled decorator because of bug in older PyQt releases   #@pyqtSlot(QModelIndex) @@ -298,6 +308,16 @@
  if index.column() == COL_PATH:   self.tv.model().toggleRow(index)   + # Disabled decorator because of bug in older PyQt releases + #@pyqtSlot(QModelIndex) + def onRowDoubleClicked(self, index): + 'tree view emitted a doubleClicked signal, index guarunteed valid' + path, status, mst, u, ext, sz = self.tv.model().getRow(index) + if status in 'MAR!': + self.actions.allactions[0].trigger() + elif status == 'S': + self.linkActivated.emit(u'subrepo:'+hglib.tounicode(path)) +   @pyqtSlot(QString)   def setFilter(self, match):   self.tv.model().setFilter(match) @@ -328,9 +348,17 @@
  else:   return []   + @pyqtSlot(QItemSelection, QItemSelection) + def onSelectionChange(self, selected, deselected): + selrows = [] + for index in self.tv.selectedRows(): + path, status, mst, u, ext, sz = self.tv.model().getRow(index) + selrows.append((set(status+mst.lower()), path)) + self.actions.updateActionSensitivity(selrows) +   # Disabled decorator because of bug in older PyQt releases   #@pyqtSlot(QModelIndex, QModelIndex) - def currentChanged(self, index, old): + def onCurrentChange(self, index, old):   'Connected to treeview "currentChanged" signal'   row = index.model().getRow(index)   if row is None: @@ -396,7 +424,7 @@
     class WctxFileTree(QTreeView): - menuAction = pyqtSignal() + menuRequest = pyqtSignal(QPoint, object)     def __init__(self, repo, parent=None):   QTreeView.__init__(self, parent) @@ -416,15 +444,7 @@
  if event.key() == 32:   for index in self.selectedRows():   self.model().toggleRow(index) - if event.key() == Qt.Key_D and event.modifiers() == Qt.ControlModifier: - selfiles = [] - for index in self.selectedRows(): - selfiles.append(self.model().getRow(index)[COL_PATH]) - dlg = visdiff.visualdiff(self.repo.ui, self.repo, selfiles, {}) - if dlg: - dlg.exec_() - else: - return super(WctxFileTree, self).keyPressEvent(event) + return super(WctxFileTree, self).keyPressEvent(event)     def dragObject(self):   urls = [] @@ -458,10 +478,8 @@
  for index in self.selectedRows():   path, status, mst, u, ext, sz = self.model().getRow(index)   selrows.append((set(status+mst.lower()), path)) - point = self.mapToGlobal(point) - action = wctxactions.wctxactions(self, point, self.repo, selrows) - if action: - self.menuAction.emit() + if selrows: + self.menuRequest.emit(self.mapToGlobal(point), selrows)     def selectedRows(self):   return self.selectionModel().selectedRows()
 
23
24
25
26
27
28
29
30
31
32
33
34
 
128
129
130
131
 
132
133
134
 
23
24
25
 
 
 
 
 
 
26
27
28
 
122
123
124
 
125
126
127
128
@@ -23,12 +23,6 @@
 from PyQt4.QtCore import *  from PyQt4.QtGui import *   -try: - import win32con - openflags = win32con.CREATE_NO_WINDOW -except ImportError: - openflags = 0 -  # Match parent2 first, so 'parent1?' will match both parent1 and parent  _regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel|repo|phash1|phash2|chash)'   @@ -128,7 +122,7 @@
  cmdline = util.quotecommand(cmdline)   try:   proc = subprocess.Popen(cmdline, shell=True, - creationflags=openflags, + creationflags=qtlib.openflags,   stderr=subprocess.PIPE,   stdout=subprocess.PIPE,   stdin=subprocess.PIPE)
 
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
 
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
 
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
 
239
240
241
242
243
244
245
 
 
 
 
246
247
248
 
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
 
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
 
197
198
199
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
201
202
203
 
226
227
228
 
 
 
 
229
230
231
232
233
234
235
@@ -7,64 +7,109 @@
   import os  import re -import subprocess    from mercurial import util, error, merge, commands  from tortoisehg.hgqt import qtlib, htmlui, visdiff  from tortoisehg.util import hglib, shlib  from tortoisehg.hgqt.i18n import _   -from PyQt4.QtCore import Qt +from PyQt4.QtCore import Qt, QObject  from PyQt4.QtGui import *   -def wctxactions(parent, point, repo, selrows): - if not selrows: - return - alltypes = set() - for t, path in selrows: - alltypes |= t +class WctxActions(QObject): + 'container class for working context actions'   - def make(text, func, types, icon=None): - files = [f for t, f in selrows if t & types] - if not files: - return None - action = menu.addAction(text) - if icon: - action.setIcon(qtlib.getmenuicon(icon)) - action.args = (func, parent, files, repo) - action.run = lambda: run(*action.args) - action.triggered.connect(action.run) - return action + def __init__(self, repo, parent): + super(WctxActions, self).__init__(parent)   - if hasattr(parent, 'contextmenu'): - menu = parent.contextmenu + self.menu = QMenu(parent) + self.repo = repo + allactions = [] + + def make(text, func, types, icon=None, keys=None): + action = QAction(text, parent) + action._filetypes = types + action._runfunc = func + if icon: + action.setIcon(qtlib.getmenuicon(icon)) + if keys: + action.setShortcut(QKeySequence(keys)) + action.triggered.connect(self.runAction) + parent.addAction(action) + allactions.append(action) + + make(_('&Visual Diff'), vdiff, frozenset('MAR!'), 'visualdiff', 'CTRL+D') + make(_('Copy patch'), copyPatch, frozenset('MAR!'), 'copy-patch') + make(_('Edit'), edit, frozenset('MACI?'), 'edit-file', 'SHIFT+CTRL+E') + make(_('View missing'), viewmissing, frozenset('R!')) + allactions.append(None) + make(_('&Revert'), revert, frozenset('MAR!'), 'hg-revert') + make(_('&Add'), add, frozenset('R'), 'fileadd') + allactions.append(None) + make(_('File History'), log, frozenset('MARC!'), 'hg-log') + make(_('&Annotate'), annotate, frozenset('MARC!'), 'hg-annotate') + allactions.append(None) + make(_('&Forget'), forget, frozenset('MAC!'), 'filedelete') + make(_('&Add'), add, frozenset('I?'), 'fileadd') + make(_('&Detect Renames...'), guessRename, frozenset('A?!'), + 'detect_rename') + make(_('&Ignore'), ignore, frozenset('?'), 'ignore') + make(_('Remove versioned'), remove, frozenset('C'), 'remove') + make(_('&Delete unversioned'), delete, frozenset('?I'), 'hg-purge') + allactions.append(None) + make(_('Mark unresolved'), unmark, frozenset('r')) + make(_('Mark resolved'), mark, frozenset('u')) + self.allactions = allactions + + def updateActionSensitivity(self, selrows): + 'Enable/Disable permanent actions based on current selection' + self.selrows = selrows + alltypes = set() + for types, wfile in selrows: + alltypes |= types + for action in self.allactions: + if action is not None: + action.setEnabled(bool(action._filetypes & alltypes)) + + def makeMenu(self, selrows): + self.selrows = selrows + repo, menu = self.repo, self.menu + + alltypes = set() + for types, wfile in selrows: + alltypes |= types +   menu.clear() - else: - menu = QMenu(parent) - parent.contextmenu = menu - make(_('&Visual Diff'), vdiff, frozenset('MAR!'), 'visualdiff') - make(_('Copy patch'), copyPatch, frozenset('MAR!'), 'copy-patch') - make(_('Edit'), edit, frozenset('MACI?'), 'edit-file') - make(_('View missing'), viewmissing, frozenset('R!')) - if len(repo.parents()) > 1: - make(_('View other'), viewother, frozenset('MA')) - menu.addSeparator() - make(_('&Revert'), revert, frozenset('MAR!'), 'hg-revert') - make(_('&Add'), add, frozenset('R'), 'fileadd') - menu.addSeparator() - make(_('File History'), log, frozenset('MARC!'), 'hg-log') - make(_('&Annotate'), annotate, frozenset('MARC!'), 'hg-annotate') - menu.addSeparator() - make(_('&Forget'), forget, frozenset('MAC!'), 'filedelete') - make(_('&Add'), add, frozenset('I?'), 'fileadd') - make(_('&Detect Renames...'), guessRename, frozenset('A?!'), 'detect_rename') - make(_('&Ignore'), ignore, frozenset('?'), 'ignore') - make(_('Remove versioned'), remove, frozenset('C'), 'remove') - make(_('&Delete unversioned'), delete, frozenset('?I'), 'hg-purge') - if len(selrows) == 1: - menu.addSeparator() + addedActions = False + for action in self.allactions: + if action is None and addedActions: + menu.addSeparator() + addedActions = False + elif action._filetypes & alltypes: + menu.addAction(action) + addedActions = True + + def make(text, func, types, icon=None): + if not types & alltypes: + return + action = menu.addAction(text) + action._filetypes = types + action._runfunc = func + if icon: + action.setIcon(qtlib.getmenuicon(icon)) + action.triggered.connect(self.runAction) + + if len(repo.parents()) > 1: + make(_('View other'), viewother, frozenset('MA')) + + if len(selrows) == 1: + menu.addSeparator() + make(_('&Copy...'), copy, frozenset('MC'), 'edit-copy') + make(_('Rename...'), rename, frozenset('MC'), 'hg-rename') + + # Add 'was renamed from' actions for unknown files   t, path = selrows[0] - wctx = repo[None] + wctx = self.repo[None]   if t & frozenset('?') and wctx.deleted():   rmenu = QMenu(_('Was renamed from'))   for d in wctx.deleted()[:15]: @@ -73,63 +118,61 @@
  a.triggered.connect(lambda: renamefromto(repo, deleted, path))   mkaction(d)   menu.addMenu(rmenu) - else: - make(_('&Copy...'), copy, frozenset('MC'), 'edit-copy') - make(_('Rename...'), rename, frozenset('MC'), 'hg-rename') - menu.addSeparator() - make(_('Mark unresolved'), unmark, frozenset('r')) - make(_('Mark resolved'), mark, frozenset('u')) - f = make(_('Restart Merge...'), resolve, frozenset('u')) - if f: - files = [f for t, f in selrows if 'u' in t] - rmenu = QMenu(_('Restart merge with')) - for tool in hglib.mergetools(repo.ui): - def mkaction(rtool): - a = rmenu.addAction(hglib.tounicode(rtool)) - a.triggered.connect(lambda: resolve_with(rtool, repo, files)) - mkaction(tool) - menu.addMenu(rmenu) - return menu.exec_(point)   -def run(func, parent, files, repo): - 'run wrapper for all action methods' - hu = htmlui.htmlui() - name = func.__name__.title() - notify = False - cwd = os.getcwd() - try: - os.chdir(repo.root) + # Add restart merge actions for resolved files + if alltypes & frozenset('u'): + f = make(_('Restart Merge...'), resolve, frozenset('u')) + files = [f for t, f in selrows if 'u' in t] + rmenu = QMenu(_('Restart merge with')) + for tool in hglib.mergetools(repo.ui): + def mkaction(rtool): + a = rmenu.addAction(hglib.tounicode(rtool)) + a.triggered.connect(lambda: resolve_with(rtool, repo, files)) + mkaction(tool) + menu.addMenu(rmenu) + return menu + + def runAction(self): + 'run wrapper for all action methods' + + repo, action, parent = self.repo, self.sender(), self.parent() + func = action._runfunc + files = [wfile for t, wfile in self.selrows if t & action._filetypes] + + hu = htmlui.htmlui() + name = func.__name__.title() + notify = False + cwd = os.getcwd()   try: - # All operations should quietly succeed. Any error should - # result in a message box - notify = func(parent, hu, repo, files) - o, e = hu.getdata() - if e: - QMessageBox.warning(parent, name + _(' errors'), str(e)) - elif o: - QMessageBox.information(parent, name + _(' output'), str(o)) - elif notify: - wfiles = [repo.wjoin(x) for x in files] - shlib.shell_notify(wfiles) - except (IOError, OSError), e: - err = hglib.tounicode(str(e)) - QMessageBox.critical(parent, name + _(' Aborted'), err) - except util.Abort, e: - if e.hint: - err = _('%s (hint: %s)') % (hglib.tounicode(str(e)), - hglib.tounicode(e.hint)) - else: + os.chdir(repo.root) + try: + # All operations should quietly succeed. Any error should + # result in a message box + notify = func(parent, hu, repo, files) + o, e = hu.getdata() + if e: + QMessageBox.warning(parent, name + _(' errors'), str(e)) + elif o: + QMessageBox.information(parent, name + _(' output'), str(o)) + elif notify: + wfiles = [repo.wjoin(x) for x in files] + shlib.shell_notify(wfiles) + except (IOError, OSError), e:   err = hglib.tounicode(str(e)) - QMessageBox.critical(parent, name + _(' Aborted'), err) - except (error.LookupError), e: - err = hglib.tounicode(str(e)) - QMessageBox.critical(parent, name + _(' Aborted'), err) - except NotImplementedError: - QMessageBox.critical(parent, name + _(' not implemented'), - 'Please add it :)') - finally: - os.chdir(cwd) - return notify + QMessageBox.critical(parent, name + _(' Aborted'), err) + except util.Abort, e: + if e.hint: + err = _('%s (hint: %s)') % (hglib.tounicode(str(e)), + hglib.tounicode(e.hint)) + else: + err = hglib.tounicode(str(e)) + QMessageBox.critical(parent, name + _(' Aborted'), err) + except (error.LookupError), e: + err = hglib.tounicode(str(e)) + QMessageBox.critical(parent, name + _(' Aborted'), err) + finally: + os.chdir(cwd) + return notify    def renamefromto(repo, deleted, unknown):   repo[None].copy(deleted, unknown) @@ -154,63 +197,7 @@
  dlg.exec_()    def edit(parent, ui, repo, files, lineno=None, search=None): - files = [util.shellquote(util.localpath(f)) for f in files] - editor = 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 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=visdiff.openflags, - stderr=None, stdout=None, stdin=None) - except (OSError, EnvironmentError), e: - QMessageBox.warning(parent, - _('Editor launch failure'), - _('%s : %s') % (cmd, str(e))) - return False - + qtlib.editfiles(repo, files, lineno, search, parent)    def viewmissing(parent, ui, repo, files):   base, _ = visdiff.snapshot(repo, files, repo['.']) @@ -239,10 +226,10 @@
  commands.revert(ui, repo, *files, **revertopts)   else:   res = qtlib.CustomPrompt( - _('Confirm Revert'), - _('Revert local file changes?'), parent, - (_('&Revert with backup'), _('&Discard changes'), - _('Cancel')), 2, 2, files).run() + _('Confirm Revert'), + _('Revert local file changes?'), parent, + (_('&Revert with backup'), _('&Discard changes'), + _('Cancel')), 2, 2, files).run()   if res == 2:   return False   if res == 1:
 
348
349
350
351
 
 
 
 
 
 
 
 
352
353
354
 
483
484
485
486
 
487
488
489
 
348
349
350
 
351
352
353
354
355
356
357
358
359
360
361
 
490
491
492
 
493
494
495
496
@@ -348,7 +348,14 @@
  repopath = hglib.fromunicode(repopath)   self._openRepo(path=repopath, reuse=reuse)   - @pyqtSlot(unicode) + @pyqtSlot(QString) + def openLinkedRepo(self, path): + self.showRepo(path) + rw = self.repoTabsWidget.currentWidget() + if rw: + rw.taskTabsWidget.setCurrentIndex(rw.commitTabIndex) + + @pyqtSlot(QString)   def showRepo(self, path):   """Activate the repo tab or open it if not available [unicode]"""   for i in xrange(self.repoTabsWidget.count()): @@ -483,7 +490,7 @@
  rw.output.connect(self.log.output)   rw.makeLogVisible.connect(self.log.setShown)   rw.revisionSelected.connect(self.updateHistoryActions) - rw.repoLinkClicked.connect(self.showRepo) + rw.repoLinkClicked.connect(self.openLinkedRepo)   rw.taskTabsWidget.currentChanged.connect(self.updateTaskViewMenu)   rw.toolbarVisibilityChanged.connect(self.updateToolBarActions)