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() |