src/eric7/Plugins/VcsPlugins/vcsGit/GitBisectLogBrowserDialog.py

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

eric ide

mercurial