Plugins/VcsPlugins/vcsGit/GitBisectLogBrowserDialog.py

changeset 6020
baf6da1ae288
child 6048
82ad8ec9548c
equal deleted inserted replaced
6019:58ecdaf0b789 6020:baf6da1ae288
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2014 - 2017 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to browse the bisect log history.
8 """
9
10 from __future__ import unicode_literals
11 try:
12 str = unicode
13 except NameError:
14 pass
15
16 import os
17
18 from PyQt5.QtCore import pyqtSlot, Qt, QPoint, QProcess, QTimer
19 from PyQt5.QtGui import QCursor
20 from PyQt5.QtWidgets import QWidget, QDialogButtonBox, QHeaderView, \
21 QTreeWidgetItem, QApplication, QLineEdit
22
23 from E5Gui import E5MessageBox
24
25 from .Ui_GitBisectLogBrowserDialog import Ui_GitBisectLogBrowserDialog
26
27 from .GitUtilities import strToQByteArray
28
29 import Preferences
30
31
32 class GitBisectLogBrowserDialog(QWidget, Ui_GitBisectLogBrowserDialog):
33 """
34 Class implementing a dialog to browse the bisect log history.
35 """
36 CommitIdColumn = 0
37 OperationColumn = 1
38 SubjectColumn = 2
39
40 def __init__(self, vcs, parent=None):
41 """
42 Constructor
43
44 @param vcs reference to the vcs object
45 @param parent reference to the parent widget (QWidget)
46 """
47 super(GitBisectLogBrowserDialog, self).__init__(parent)
48 self.setupUi(self)
49
50 self.__position = QPoint()
51
52 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
53 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
54
55 self.logTree.headerItem().setText(self.logTree.columnCount(), "")
56
57 self.refreshButton = self.buttonBox.addButton(
58 self.tr("&Refresh"), QDialogButtonBox.ActionRole)
59 self.refreshButton.setToolTip(
60 self.tr("Press to refresh the list of commits"))
61 self.refreshButton.setEnabled(False)
62
63 self.vcs = vcs
64
65 self.repodir = ""
66 self.__currentCommitId = ""
67
68 self.__initData()
69 self.__resetUI()
70
71 self.process = QProcess()
72 self.process.finished.connect(self.__procFinished)
73 self.process.readyReadStandardOutput.connect(self.__readStdout)
74 self.process.readyReadStandardError.connect(self.__readStderr)
75
76 def __initData(self):
77 """
78 Private method to (re-)initialize some data.
79 """
80 self.buf = [] # buffer for stdout
81
82 def closeEvent(self, e):
83 """
84 Protected slot implementing a close event handler.
85
86 @param e close event (QCloseEvent)
87 """
88 if self.process is not None and \
89 self.process.state() != QProcess.NotRunning:
90 self.process.terminate()
91 QTimer.singleShot(2000, self.process.kill)
92 self.process.waitForFinished(3000)
93
94 self.__position = self.pos()
95
96 e.accept()
97
98 def show(self):
99 """
100 Public slot to show the dialog.
101 """
102 if not self.__position.isNull():
103 self.move(self.__position)
104 self.__resetUI()
105
106 super(GitBisectLogBrowserDialog, self).show()
107
108 def __resetUI(self):
109 """
110 Private method to reset the user interface.
111 """
112 self.logTree.clear()
113
114 def __resizeColumnsLog(self):
115 """
116 Private method to resize the log tree columns.
117 """
118 self.logTree.header().resizeSections(QHeaderView.ResizeToContents)
119 self.logTree.header().setStretchLastSection(True)
120
121 def __generateLogItem(self, commitId, operation, subject):
122 """
123 Private method to generate a bisect log tree entry.
124
125 @param commitId commit id info (string)
126 @param operation bisect operation (string)
127 @param subject subject of the bisect log entry (string)
128 @return reference to the generated item (QTreeWidgetItem)
129 """
130 columnLabels = [
131 commitId,
132 operation,
133 subject,
134 ]
135 itm = QTreeWidgetItem(self.logTree, columnLabels)
136 return itm
137
138 def __getLogEntries(self):
139 """
140 Private method to retrieve bisect log entries from the repository.
141 """
142 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
143 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
144 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
145
146 self.inputGroup.setEnabled(True)
147 self.inputGroup.show()
148 self.refreshButton.setEnabled(False)
149
150 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
151 QApplication.processEvents()
152
153 self.buf = []
154 self.cancelled = False
155 self.errors.clear()
156 self.intercept = False
157
158 args = self.vcs.initCommand("bisect")
159 args.append("log")
160
161 self.process.kill()
162
163 self.process.setWorkingDirectory(self.repodir)
164
165 self.inputGroup.setEnabled(True)
166 self.inputGroup.show()
167
168 self.process.start('git', args)
169 procStarted = self.process.waitForStarted(5000)
170 if not procStarted:
171 self.inputGroup.setEnabled(False)
172 self.inputGroup.hide()
173 E5MessageBox.critical(
174 self,
175 self.tr('Process Generation Error'),
176 self.tr(
177 'The process {0} could not be started. '
178 'Ensure, that it is in the search path.'
179 ).format('git'))
180
181 def start(self, projectdir):
182 """
183 Public slot to start the git bisect log command.
184
185 @param projectdir directory name of the project (string)
186 """
187 self.errorGroup.hide()
188 QApplication.processEvents()
189
190 self.__initData()
191
192 # find the root of the repo
193 self.repodir = projectdir
194 while not os.path.isdir(os.path.join(self.repodir, self.vcs.adminDir)):
195 self.repodir = os.path.dirname(self.repodir)
196 if os.path.splitdrive(self.repodir)[1] == os.sep:
197 return
198
199 self.activateWindow()
200 self.raise_()
201
202 self.logTree.clear()
203 self.__getLogEntries()
204
205 def __procFinished(self, exitCode, exitStatus):
206 """
207 Private slot connected to the finished signal.
208
209 @param exitCode exit code of the process (integer)
210 @param exitStatus exit status of the process (QProcess.ExitStatus)
211 """
212 self.__processBuffer()
213 self.__finish()
214
215 def __finish(self):
216 """
217 Private slot called when the process finished or the user pressed
218 the button.
219 """
220 if self.process is not None and \
221 self.process.state() != QProcess.NotRunning:
222 self.process.terminate()
223 QTimer.singleShot(2000, self.process.kill)
224 self.process.waitForFinished(3000)
225
226 QApplication.restoreOverrideCursor()
227
228 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
229 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
230 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
231
232 self.inputGroup.setEnabled(False)
233 self.inputGroup.hide()
234 self.refreshButton.setEnabled(True)
235
236 def __processBuffer(self):
237 """
238 Private method to process the buffered output of the git log command.
239 """
240 for line in self.buf:
241 line = line.rstrip()
242
243 if line.startswith("# "):
244 operation, commitId, subject = line[2:].split(None, 2)
245 # commit is surrounded by [...], abbreviate to 20 chars
246 commitId = commitId[1:-1][:20]
247 # operation is followed by ':'
248 operation = operation[:-1]
249 self.__generateLogItem(commitId, operation, subject)
250
251 self.__resizeColumnsLog()
252 self.logTree.setCurrentItem(self.logTree.topLevelItem(0))
253
254 # restore current item
255 if self.__currentCommitId:
256 items = self.logTree.findItems(
257 self.__currentCommitId, Qt.MatchExactly, self.CommitIdColumn)
258 if items:
259 self.logTree.setCurrentItem(items[0])
260 self.__currentCommitId = ""
261
262 def __readStdout(self):
263 """
264 Private slot to handle the readyReadStandardOutput signal.
265
266 It reads the output of the process and inserts it into a buffer.
267 """
268 self.process.setReadChannel(QProcess.StandardOutput)
269
270 while self.process.canReadLine():
271 line = str(self.process.readLine(),
272 Preferences.getSystem("IOEncoding"),
273 'replace')
274 self.buf.append(line)
275
276 def __readStderr(self):
277 """
278 Private slot to handle the readyReadStandardError signal.
279
280 It reads the error output of the process and inserts it into the
281 error pane.
282 """
283 if self.process is not None:
284 s = str(self.process.readAllStandardError(),
285 Preferences.getSystem("IOEncoding"),
286 'replace')
287 self.__showError(s)
288
289 def __showError(self, out):
290 """
291 Private slot to show some error.
292
293 @param out error to be shown (string)
294 """
295 self.errorGroup.show()
296 self.errors.insertPlainText(out)
297 self.errors.ensureCursorVisible()
298
299 def on_buttonBox_clicked(self, button):
300 """
301 Private slot called by a button of the button box clicked.
302
303 @param button button that was clicked (QAbstractButton)
304 """
305 if button == self.buttonBox.button(QDialogButtonBox.Close):
306 self.close()
307 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
308 self.cancelled = True
309 self.__finish()
310 elif button == self.refreshButton:
311 self.on_refreshButton_clicked()
312
313 @pyqtSlot()
314 def on_refreshButton_clicked(self):
315 """
316 Private slot to refresh the log.
317 """
318 # save the current item's commit ID
319 itm = self.logTree.currentItem()
320 if itm is not None:
321 self.__currentCommitId = itm.text(self.CommitIdColumn)
322 else:
323 self.__currentCommitId = ""
324
325 self.start(self.repodir)
326
327 def on_passwordCheckBox_toggled(self, isOn):
328 """
329 Private slot to handle the password checkbox toggled.
330
331 @param isOn flag indicating the status of the check box (boolean)
332 """
333 if isOn:
334 self.input.setEchoMode(QLineEdit.Password)
335 else:
336 self.input.setEchoMode(QLineEdit.Normal)
337
338 @pyqtSlot()
339 def on_sendButton_clicked(self):
340 """
341 Private slot to send the input to the git process.
342 """
343 inputTxt = self.input.text()
344 inputTxt += os.linesep
345
346 if self.passwordCheckBox.isChecked():
347 self.errors.insertPlainText(os.linesep)
348 self.errors.ensureCursorVisible()
349 else:
350 self.errors.insertPlainText(inputTxt)
351 self.errors.ensureCursorVisible()
352 self.errorGroup.show()
353
354 self.process.write(strToQByteArray(inputTxt))
355
356 self.passwordCheckBox.setChecked(False)
357 self.input.clear()
358
359 def on_input_returnPressed(self):
360 """
361 Private slot to handle the press of the return key in the input field.
362 """
363 self.intercept = True
364 self.on_sendButton_clicked()
365
366 def keyPressEvent(self, evt):
367 """
368 Protected slot to handle a key press event.
369
370 @param evt the key press event (QKeyEvent)
371 """
372 if self.intercept:
373 self.intercept = False
374 evt.accept()
375 return
376 super(GitBisectLogBrowserDialog, self).keyPressEvent(evt)

eric ide

mercurial