eric7/Plugins/VcsPlugins/vcsGit/GitBisectLogBrowserDialog.py

branch
eric7
changeset 8312
800c432b34c8
parent 8218
7c09585bd960
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2014 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to browse the bisect log history.
8 """
9
10 import os
11
12 from PyQt5.QtCore import pyqtSlot, Qt, QPoint, QProcess, QTimer
13 from PyQt5.QtWidgets import (
14 QWidget, QDialogButtonBox, QHeaderView, QTreeWidgetItem, QApplication,
15 QLineEdit
16 )
17
18 from E5Gui import E5MessageBox
19 from E5Gui.E5OverrideCursor import E5OverrideCursorProcess
20
21 from .Ui_GitBisectLogBrowserDialog import Ui_GitBisectLogBrowserDialog
22
23 import Preferences
24 from Globals import strToQByteArray
25
26
27 class GitBisectLogBrowserDialog(QWidget, Ui_GitBisectLogBrowserDialog):
28 """
29 Class implementing a dialog to browse the bisect log history.
30 """
31 CommitIdColumn = 0
32 OperationColumn = 1
33 SubjectColumn = 2
34
35 def __init__(self, vcs, parent=None):
36 """
37 Constructor
38
39 @param vcs reference to the vcs object
40 @param parent reference to the parent widget (QWidget)
41 """
42 super().__init__(parent)
43 self.setupUi(self)
44
45 self.__position = QPoint()
46
47 self.buttonBox.button(
48 QDialogButtonBox.StandardButton.Close).setEnabled(False)
49 self.buttonBox.button(
50 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
51
52 self.logTree.headerItem().setText(self.logTree.columnCount(), "")
53
54 self.refreshButton = self.buttonBox.addButton(
55 self.tr("&Refresh"), QDialogButtonBox.ButtonRole.ActionRole)
56 self.refreshButton.setToolTip(
57 self.tr("Press to refresh the list of commits"))
58 self.refreshButton.setEnabled(False)
59
60 self.vcs = vcs
61
62 self.repodir = ""
63 self.__currentCommitId = ""
64
65 self.__initData()
66 self.__resetUI()
67
68 self.__process = E5OverrideCursorProcess()
69 self.__process.finished.connect(self.__procFinished)
70 self.__process.readyReadStandardOutput.connect(self.__readStdout)
71 self.__process.readyReadStandardError.connect(self.__readStderr)
72
73 def __initData(self):
74 """
75 Private method to (re-)initialize some data.
76 """
77 self.buf = [] # buffer for stdout
78
79 def closeEvent(self, e):
80 """
81 Protected slot implementing a close event handler.
82
83 @param e close event (QCloseEvent)
84 """
85 if (
86 self.__process is not None and
87 self.__process.state() != QProcess.ProcessState.NotRunning
88 ):
89 self.__process.terminate()
90 QTimer.singleShot(2000, self.__process.kill)
91 self.__process.waitForFinished(3000)
92
93 self.__position = self.pos()
94
95 e.accept()
96
97 def show(self):
98 """
99 Public slot to show the dialog.
100 """
101 if not self.__position.isNull():
102 self.move(self.__position)
103 self.__resetUI()
104
105 super().show()
106
107 def __resetUI(self):
108 """
109 Private method to reset the user interface.
110 """
111 self.logTree.clear()
112
113 def __resizeColumnsLog(self):
114 """
115 Private method to resize the log tree columns.
116 """
117 self.logTree.header().resizeSections(
118 QHeaderView.ResizeMode.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(
143 QDialogButtonBox.StandardButton.Close).setEnabled(False)
144 self.buttonBox.button(
145 QDialogButtonBox.StandardButton.Cancel).setEnabled(True)
146 self.buttonBox.button(
147 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
148
149 self.inputGroup.setEnabled(True)
150 self.inputGroup.show()
151 self.refreshButton.setEnabled(False)
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 (
221 self.__process is not None and
222 self.__process.state() != QProcess.ProcessState.NotRunning
223 ):
224 self.__process.terminate()
225 QTimer.singleShot(2000, self.__process.kill)
226 self.__process.waitForFinished(3000)
227
228 self.buttonBox.button(
229 QDialogButtonBox.StandardButton.Close).setEnabled(True)
230 self.buttonBox.button(
231 QDialogButtonBox.StandardButton.Cancel).setEnabled(False)
232 self.buttonBox.button(
233 QDialogButtonBox.StandardButton.Close).setDefault(True)
234
235 self.inputGroup.setEnabled(False)
236 self.inputGroup.hide()
237 self.refreshButton.setEnabled(True)
238
239 def __processBuffer(self):
240 """
241 Private method to process the buffered output of the git log command.
242 """
243 for line in self.buf:
244 line = line.rstrip()
245
246 if line.startswith("# "):
247 operation, commitId, subject = line[2:].split(None, 2)
248 # commit is surrounded by [...], abbreviate to 20 chars
249 commitId = commitId[1:-1][:20]
250 # operation is followed by ':'
251 operation = operation[:-1]
252 self.__generateLogItem(commitId, operation, subject)
253
254 self.__resizeColumnsLog()
255 self.logTree.setCurrentItem(self.logTree.topLevelItem(0))
256
257 # restore current item
258 if self.__currentCommitId:
259 items = self.logTree.findItems(
260 self.__currentCommitId, Qt.MatchFlag.MatchExactly,
261 self.CommitIdColumn)
262 if items:
263 self.logTree.setCurrentItem(items[0])
264 self.__currentCommitId = ""
265
266 def __readStdout(self):
267 """
268 Private slot to handle the readyReadStandardOutput signal.
269
270 It reads the output of the process and inserts it into a buffer.
271 """
272 self.__process.setReadChannel(QProcess.ProcessChannel.StandardOutput)
273
274 while self.__process.canReadLine():
275 line = str(self.__process.readLine(),
276 Preferences.getSystem("IOEncoding"),
277 'replace')
278 self.buf.append(line)
279
280 def __readStderr(self):
281 """
282 Private slot to handle the readyReadStandardError signal.
283
284 It reads the error output of the process and inserts it into the
285 error pane.
286 """
287 if self.__process is not None:
288 s = str(self.__process.readAllStandardError(),
289 Preferences.getSystem("IOEncoding"),
290 'replace')
291 self.__showError(s)
292
293 def __showError(self, out):
294 """
295 Private slot to show some error.
296
297 @param out error to be shown (string)
298 """
299 self.errorGroup.show()
300 self.errors.insertPlainText(out)
301 self.errors.ensureCursorVisible()
302
303 def on_buttonBox_clicked(self, button):
304 """
305 Private slot called by a button of the button box clicked.
306
307 @param button button that was clicked (QAbstractButton)
308 """
309 if button == self.buttonBox.button(
310 QDialogButtonBox.StandardButton.Close
311 ):
312 self.close()
313 elif button == self.buttonBox.button(
314 QDialogButtonBox.StandardButton.Cancel
315 ):
316 self.cancelled = True
317 self.__finish()
318 elif button == self.refreshButton:
319 self.on_refreshButton_clicked()
320
321 @pyqtSlot()
322 def on_refreshButton_clicked(self):
323 """
324 Private slot to refresh the log.
325 """
326 # save the current item's commit ID
327 itm = self.logTree.currentItem()
328 if itm is not None:
329 self.__currentCommitId = itm.text(self.CommitIdColumn)
330 else:
331 self.__currentCommitId = ""
332
333 self.start(self.repodir)
334
335 def on_passwordCheckBox_toggled(self, isOn):
336 """
337 Private slot to handle the password checkbox toggled.
338
339 @param isOn flag indicating the status of the check box (boolean)
340 """
341 if isOn:
342 self.input.setEchoMode(QLineEdit.EchoMode.Password)
343 else:
344 self.input.setEchoMode(QLineEdit.EchoMode.Normal)
345
346 @pyqtSlot()
347 def on_sendButton_clicked(self):
348 """
349 Private slot to send the input to the git process.
350 """
351 inputTxt = self.input.text()
352 inputTxt += os.linesep
353
354 if self.passwordCheckBox.isChecked():
355 self.errors.insertPlainText(os.linesep)
356 self.errors.ensureCursorVisible()
357 else:
358 self.errors.insertPlainText(inputTxt)
359 self.errors.ensureCursorVisible()
360 self.errorGroup.show()
361
362 self.__process.write(strToQByteArray(inputTxt))
363
364 self.passwordCheckBox.setChecked(False)
365 self.input.clear()
366
367 def on_input_returnPressed(self):
368 """
369 Private slot to handle the press of the return key in the input field.
370 """
371 self.intercept = True
372 self.on_sendButton_clicked()
373
374 def keyPressEvent(self, evt):
375 """
376 Protected slot to handle a key press event.
377
378 @param evt the key press event (QKeyEvent)
379 """
380 if self.intercept:
381 self.intercept = False
382 evt.accept()
383 return
384 super().keyPressEvent(evt)

eric ide

mercurial