eric6/Plugins/VcsPlugins/vcsMercurial/HgLogBrowserDialog.py

changeset 7370
5fb53279f2df
parent 7360
9190402e4505
child 7454
a4392387052c
equal deleted inserted replaced
7369:dbeeed55df08 7370:5fb53279f2df
5 5
6 """ 6 """
7 Module implementing a dialog to browse the log history. 7 Module implementing a dialog to browse the log history.
8 """ 8 """
9 9
10
11 import os 10 import os
12 import re 11 import re
13 import collections 12 import collections
14 13
15 from PyQt5.QtCore import ( 14 from PyQt5.QtCore import pyqtSlot, Qt, QDate, QRegExp, QSize, QPoint, QFileInfo
16 pyqtSlot, Qt, QDate, QProcess, QTimer, QRegExp, QSize, QPoint, QFileInfo
17 )
18 from PyQt5.QtGui import ( 15 from PyQt5.QtGui import (
19 QCursor, QColor, QPixmap, QPainter, QPen, QBrush, QIcon, QTextCursor 16 QCursor, QColor, QPixmap, QPainter, QPen, QBrush, QIcon, QTextCursor
20 ) 17 )
21 from PyQt5.QtWidgets import ( 18 from PyQt5.QtWidgets import (
22 QWidget, QDialogButtonBox, QHeaderView, QTreeWidgetItem, QApplication, 19 QWidget, QDialogButtonBox, QHeaderView, QTreeWidgetItem, QApplication,
32 from .HgDiffGenerator import HgDiffGenerator 29 from .HgDiffGenerator import HgDiffGenerator
33 30
34 import UI.PixmapCache 31 import UI.PixmapCache
35 import Preferences 32 import Preferences
36 import Utilities 33 import Utilities
37 from Globals import strToQByteArray
38 34
39 COLORNAMES = ["blue", "darkgreen", "red", "green", "darkblue", "purple", 35 COLORNAMES = ["blue", "darkgreen", "red", "green", "darkblue", "purple",
40 "cyan", "olive", "magenta", "darkred", "darkmagenta", 36 "cyan", "olive", "magenta", "darkred", "darkmagenta",
41 "darkcyan", "gray", "yellow"] 37 "darkcyan", "gray", "yellow"]
42 COLORS = [str(QColor(x).name()) for x in COLORNAMES] 38 COLORS = [str(QColor(x).name()) for x in COLORNAMES]
198 self.__incomingRole = Qt.UserRole + 5 194 self.__incomingRole = Qt.UserRole + 5
199 195
200 # roles used in the file tree 196 # roles used in the file tree
201 self.__diffFileLineRole = Qt.UserRole 197 self.__diffFileLineRole = Qt.UserRole
202 198
203 if self.__hgClient:
204 self.process = None
205 else:
206 self.process = QProcess()
207 self.process.finished.connect(self.__procFinished)
208 self.process.readyReadStandardOutput.connect(self.__readStdout)
209 self.process.readyReadStandardError.connect(self.__readStderr)
210
211 self.flags = { 199 self.flags = {
212 'A': self.tr('Added'), 200 'A': self.tr('Added'),
213 'D': self.tr('Deleted'), 201 'D': self.tr('Deleted'),
214 'M': self.tr('Modified'), 202 'M': self.tr('Modified'),
215 } 203 }
428 """ 416 """
429 Protected slot implementing a close event handler. 417 Protected slot implementing a close event handler.
430 418
431 @param e close event (QCloseEvent) 419 @param e close event (QCloseEvent)
432 """ 420 """
433 if self.__hgClient: 421 if self.__hgClient.isExecuting():
434 if self.__hgClient.isExecuting(): 422 self.__hgClient.cancel()
435 self.__hgClient.cancel()
436 else:
437 if (
438 self.process is not None and
439 self.process.state() != QProcess.NotRunning
440 ):
441 self.process.terminate()
442 QTimer.singleShot(2000, self.process.kill)
443 self.process.waitForFinished(3000)
444 423
445 self.vcs.getPlugin().setPreferences( 424 self.vcs.getPlugin().setPreferences(
446 "LogBrowserGeometry", self.saveGeometry()) 425 "LogBrowserGeometry", self.saveGeometry())
447 self.vcs.getPlugin().setPreferences( 426 self.vcs.getPlugin().setPreferences(
448 "LogBrowserSplitterStates", [ 427 "LogBrowserSplitterStates", [
754 args.append("-r") 733 args.append("-r")
755 args.append(rev) 734 args.append(rev)
756 if not self.projectMode: 735 if not self.projectMode:
757 args.append(self.__filename) 736 args.append(self.__filename)
758 737
759 output = "" 738 output, errMsg = self.__hgClient.runcommand(args)
760 if self.__hgClient:
761 output, errMsg = self.__hgClient.runcommand(args)
762 else:
763 process = QProcess()
764 process.setWorkingDirectory(self.repodir)
765 process.start('hg', args)
766 procStarted = process.waitForStarted(5000)
767 if procStarted:
768 finished = process.waitForFinished(30000)
769 if finished and process.exitCode() == 0:
770 output = str(process.readAllStandardOutput(),
771 self.vcs.getEncoding(), 'replace')
772 else:
773 if not finished:
774 errMsg = self.tr(
775 "The hg process did not finish within 30s.")
776 else:
777 errMsg = self.tr("Could not start the hg executable.")
778
779 if errMsg:
780 E5MessageBox.critical(
781 self,
782 self.tr("Mercurial Error"),
783 errMsg)
784 739
785 if output: 740 if output:
786 parents = [int(p) for p in output.strip().splitlines()] 741 parents = [int(p) for p in output.strip().splitlines()]
787 742
788 return parents 743 return parents
794 errMsg = "" 749 errMsg = ""
795 750
796 args = self.vcs.initCommand("identify") 751 args = self.vcs.initCommand("identify")
797 args.append("-nb") 752 args.append("-nb")
798 753
799 output = "" 754 output, errMsg = self.__hgClient.runcommand(args)
800 if self.__hgClient:
801 output, errMsg = self.__hgClient.runcommand(args)
802 else:
803 process = QProcess()
804 process.setWorkingDirectory(self.repodir)
805 process.start('hg', args)
806 procStarted = process.waitForStarted(5000)
807 if procStarted:
808 finished = process.waitForFinished(30000)
809 if finished and process.exitCode() == 0:
810 output = str(process.readAllStandardOutput(),
811 self.vcs.getEncoding(), 'replace')
812 else:
813 if not finished:
814 errMsg = self.tr(
815 "The hg process did not finish within 30s.")
816 else:
817 errMsg = self.tr("Could not start the hg executable.")
818 755
819 if errMsg: 756 if errMsg:
820 E5MessageBox.critical( 757 E5MessageBox.critical(
821 self, 758 self,
822 self.tr("Mercurial Error"), 759 self.tr("Mercurial Error"),
841 errMsg = "" 778 errMsg = ""
842 779
843 args = self.vcs.initCommand("branches") 780 args = self.vcs.initCommand("branches")
844 args.append("--closed") 781 args.append("--closed")
845 782
846 output = "" 783 output, errMsg = self.__hgClient.runcommand(args)
847 if self.__hgClient:
848 output, errMsg = self.__hgClient.runcommand(args)
849 else:
850 process = QProcess()
851 process.setWorkingDirectory(self.repodir)
852 process.start('hg', args)
853 procStarted = process.waitForStarted(5000)
854 if procStarted:
855 finished = process.waitForFinished(30000)
856 if finished and process.exitCode() == 0:
857 output = str(process.readAllStandardOutput(),
858 self.vcs.getEncoding(), 'replace')
859 else:
860 if not finished:
861 errMsg = self.tr(
862 "The hg process did not finish within 30s.")
863 else:
864 errMsg = self.tr("Could not start the hg executable.")
865 784
866 if errMsg: 785 if errMsg:
867 E5MessageBox.critical( 786 E5MessageBox.critical(
868 self, 787 self,
869 self.tr("Mercurial Error"), 788 self.tr("Mercurial Error"),
886 args = self.vcs.initCommand("heads") 805 args = self.vcs.initCommand("heads")
887 args.append("--closed") 806 args.append("--closed")
888 args.append("--template") 807 args.append("--template")
889 args.append("{rev}\n") 808 args.append("{rev}\n")
890 809
891 output = "" 810 output, errMsg = self.__hgClient.runcommand(args)
892 if self.__hgClient:
893 output, errMsg = self.__hgClient.runcommand(args)
894 else:
895 process = QProcess()
896 process.setWorkingDirectory(self.repodir)
897 process.start('hg', args)
898 procStarted = process.waitForStarted(5000)
899 if procStarted:
900 finished = process.waitForFinished(30000)
901 if finished and process.exitCode() == 0:
902 output = str(process.readAllStandardOutput(),
903 self.vcs.getEncoding(), 'replace')
904 else:
905 if not finished:
906 errMsg = self.tr(
907 "The hg process did not finish within 30s.")
908 else:
909 errMsg = self.tr("Could not start the hg executable.")
910 811
911 if errMsg: 812 if errMsg:
912 E5MessageBox.critical( 813 E5MessageBox.critical(
913 self, 814 self,
914 self.tr("Mercurial Error"), 815 self.tr("Mercurial Error"),
931 """ 832 """
932 errMsg = "" 833 errMsg = ""
933 834
934 args = self.vcs.initCommand("tags") 835 args = self.vcs.initCommand("tags")
935 836
936 output = "" 837 output, errMsg = self.__hgClient.runcommand(args)
937 if self.__hgClient:
938 output, errMsg = self.__hgClient.runcommand(args)
939 else:
940 process = QProcess()
941 process.setWorkingDirectory(self.repodir)
942 process.start('hg', args)
943 procStarted = process.waitForStarted(5000)
944 if procStarted:
945 finished = process.waitForFinished(30000)
946 if finished and process.exitCode() == 0:
947 output = str(process.readAllStandardOutput(),
948 self.vcs.getEncoding(), 'replace')
949 else:
950 if not finished:
951 errMsg = self.tr(
952 "The hg process did not finish within 30s.")
953 else:
954 errMsg = self.tr("Could not start the hg executable.")
955 838
956 if errMsg: 839 if errMsg:
957 E5MessageBox.critical( 840 E5MessageBox.critical(
958 self, 841 self,
959 self.tr("Mercurial Error"), 842 self.tr("Mercurial Error"),
1135 preargs.append(self.vcs.bundleFile) 1018 preargs.append(self.vcs.bundleFile)
1136 args.append(self.vcs.bundleFile) 1019 args.append(self.vcs.bundleFile)
1137 if not self.projectMode: 1020 if not self.projectMode:
1138 args.append(self.__filename) 1021 args.append(self.__filename)
1139 1022
1140 if self.__hgClient: 1023 if preargs:
1141 self.inputGroup.setEnabled(False) 1024 out, err = self.__hgClient.runcommand(preargs)
1142 self.inputGroup.hide() 1025 else:
1143 1026 err = ""
1144 if preargs: 1027 if err:
1145 out, err = self.__hgClient.runcommand(preargs) 1028 if (
1146 else:
1147 err = ""
1148 if err:
1149 if (
1150 self.commandMode == "incoming" and
1151 self.initialCommandMode == "full_log"
1152 ):
1153 # ignore the error
1154 self.commandMode = "log"
1155 else:
1156 self.__showError(err)
1157 elif (
1158 self.commandMode != "incoming" or
1159 (self.vcs.bundleFile and
1160 os.path.exists(self.vcs.bundleFile)) or
1161 self.__bundle
1162 ):
1163 out, err = self.__hgClient.runcommand(args)
1164 self.buf = out.splitlines(True)
1165 if err:
1166 self.__showError(err)
1167 self.__processBuffer()
1168 elif (
1169 self.commandMode == "incoming" and 1029 self.commandMode == "incoming" and
1170 self.initialCommandMode == "full_log" 1030 self.initialCommandMode == "full_log"
1171 ): 1031 ):
1172 # no incoming changesets, just switch to log mode 1032 # ignore the error
1173 self.commandMode = "log" 1033 self.commandMode = "log"
1174 self.__finish() 1034 else:
1175 else: 1035 self.__showError(err)
1176 self.process.kill() 1036 elif (
1177 1037 self.commandMode != "incoming" or
1178 self.process.setWorkingDirectory(self.repodir) 1038 (self.vcs.bundleFile and
1179 1039 os.path.exists(self.vcs.bundleFile)) or
1180 if preargs: 1040 self.__bundle
1181 process = QProcess() 1041 ):
1182 process.setWorkingDirectory(self.repodir) 1042 out, err = self.__hgClient.runcommand(args)
1183 process.start('hg', args) 1043 self.buf = out.splitlines(True)
1184 procStarted = process.waitForStarted(5000) 1044 if err:
1185 if procStarted: 1045 self.__showError(err)
1186 process.waitForFinished(30000) 1046 self.__processBuffer()
1187 1047 elif (
1188 if ( 1048 self.commandMode == "incoming" and
1189 self.commandMode != "incoming" or 1049 self.initialCommandMode == "full_log"
1190 (self.vcs.bundleFile and 1050 ):
1191 os.path.exists(self.vcs.bundleFile)) or 1051 # no incoming changesets, just switch to log mode
1192 self.__bundle 1052 self.commandMode = "log"
1193 ): 1053 self.__finish()
1194 self.process.start('hg', args)
1195 procStarted = self.process.waitForStarted(5000)
1196 if not procStarted:
1197 self.inputGroup.setEnabled(False)
1198 self.inputGroup.hide()
1199 E5MessageBox.critical(
1200 self,
1201 self.tr('Process Generation Error'),
1202 self.tr(
1203 'The process {0} could not be started. '
1204 'Ensure, that it is in the search path.'
1205 ).format('hg'))
1206 else:
1207 self.__finish()
1208 1054
1209 def start(self, fn, bundle=None, isFile=False, noEntries=0): 1055 def start(self, fn, bundle=None, isFile=False, noEntries=0):
1210 """ 1056 """
1211 Public slot to start the hg log command. 1057 Public slot to start the hg log command.
1212 1058
1255 self.__identifyProject() 1101 self.__identifyProject()
1256 self.__getClosedBranches() 1102 self.__getClosedBranches()
1257 self.__getHeads() 1103 self.__getHeads()
1258 self.__getLogEntries(noEntries=noEntries) 1104 self.__getLogEntries(noEntries=noEntries)
1259 1105
1260 def __procFinished(self, exitCode, exitStatus):
1261 """
1262 Private slot connected to the finished signal.
1263
1264 @param exitCode exit code of the process (integer)
1265 @param exitStatus exit status of the process (QProcess.ExitStatus)
1266 """
1267 self.__processBuffer()
1268 self.__finish()
1269
1270 def __finish(self): 1106 def __finish(self):
1271 """ 1107 """
1272 Private slot called when the process finished or the user pressed 1108 Private slot called when the process finished or the user pressed
1273 the button. 1109 the button.
1274 """ 1110 """
1275 if (
1276 self.process is not None and
1277 self.process.state() != QProcess.NotRunning
1278 ):
1279 self.process.terminate()
1280 QTimer.singleShot(2000, self.process.kill)
1281 self.process.waitForFinished(3000)
1282
1283 QApplication.restoreOverrideCursor() 1111 QApplication.restoreOverrideCursor()
1284 1112
1285 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) 1113 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
1286 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) 1114 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
1287 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) 1115 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
1288 1116
1289 self.inputGroup.setEnabled(False)
1290 self.inputGroup.hide()
1291 self.refreshButton.setEnabled(True) 1117 self.refreshButton.setEnabled(True)
1292 1118
1293 while self.__finishCallbacks: 1119 while self.__finishCallbacks:
1294 self.__finishCallbacks.pop(0)() 1120 self.__finishCallbacks.pop(0)()
1295 1121
1494 revision, Qt.MatchExactly, self.RevisionColumn) 1320 revision, Qt.MatchExactly, self.RevisionColumn)
1495 if items: 1321 if items:
1496 items[0].setSelected(True) 1322 items[0].setSelected(True)
1497 self.__selectedRevisions = [] 1323 self.__selectedRevisions = []
1498 1324
1499 def __readStdout(self):
1500 """
1501 Private slot to handle the readyReadStandardOutput signal.
1502
1503 It reads the output of the process and inserts it into a buffer.
1504 """
1505 self.process.setReadChannel(QProcess.StandardOutput)
1506
1507 while self.process.canReadLine():
1508 line = str(self.process.readLine(), self.vcs.getEncoding(),
1509 'replace')
1510 self.buf.append(line)
1511
1512 def __readStderr(self):
1513 """
1514 Private slot to handle the readyReadStandardError signal.
1515
1516 It reads the error output of the process and inserts it into the
1517 error pane.
1518 """
1519 if self.process is not None:
1520 s = str(self.process.readAllStandardError(),
1521 self.vcs.getEncoding(), 'replace')
1522 self.__showError(s)
1523
1524 def __showError(self, out): 1325 def __showError(self, out):
1525 """ 1326 """
1526 Private slot to show some error. 1327 Private slot to show some error.
1527 1328
1528 @param out error to be shown (string) 1329 @param out error to be shown (string)
1529 """ 1330 """
1530 self.errorGroup.show() 1331 self.errorGroup.show()
1531 self.errors.insertPlainText(out) 1332 self.errors.insertPlainText(out)
1532 self.errors.ensureCursorVisible() 1333 self.errors.ensureCursorVisible()
1533
1534 if not self.__hgClient:
1535 # show input in case the process asked for some input
1536 self.inputGroup.setEnabled(True)
1537 self.inputGroup.show()
1538 1334
1539 def on_buttonBox_clicked(self, button): 1335 def on_buttonBox_clicked(self, button):
1540 """ 1336 """
1541 Private slot called by a button of the button box clicked. 1337 Private slot called by a button of the button box clicked.
1542 1338
1544 """ 1340 """
1545 if button == self.buttonBox.button(QDialogButtonBox.Close): 1341 if button == self.buttonBox.button(QDialogButtonBox.Close):
1546 self.close() 1342 self.close()
1547 elif button == self.buttonBox.button(QDialogButtonBox.Cancel): 1343 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
1548 self.cancelled = True 1344 self.cancelled = True
1549 if self.__hgClient: 1345 self.__hgClient.cancel()
1550 self.__hgClient.cancel()
1551 else:
1552 self.__finish()
1553 elif button == self.refreshButton: 1346 elif button == self.refreshButton:
1554 self.on_refreshButton_clicked() 1347 self.on_refreshButton_clicked()
1555 1348
1556 def __updateSbsSelectLabel(self): 1349 def __updateSbsSelectLabel(self):
1557 """ 1350 """
2156 else: 1949 else:
2157 self.commandMode = self.initialCommandMode 1950 self.commandMode = self.initialCommandMode
2158 self.start(self.__filename, bundle=self.__bundle, isFile=self.__isFile, 1951 self.start(self.__filename, bundle=self.__bundle, isFile=self.__isFile,
2159 noEntries=self.logTree.topLevelItemCount()) 1952 noEntries=self.logTree.topLevelItemCount())
2160 1953
2161 def on_passwordCheckBox_toggled(self, isOn):
2162 """
2163 Private slot to handle the password checkbox toggled.
2164
2165 @param isOn flag indicating the status of the check box (boolean)
2166 """
2167 if isOn:
2168 self.input.setEchoMode(QLineEdit.Password)
2169 else:
2170 self.input.setEchoMode(QLineEdit.Normal)
2171
2172 @pyqtSlot()
2173 def on_sendButton_clicked(self):
2174 """
2175 Private slot to send the input to the mercurial process.
2176 """
2177 inputTxt = self.input.text()
2178 inputTxt += os.linesep
2179
2180 if self.passwordCheckBox.isChecked():
2181 self.errors.insertPlainText(os.linesep)
2182 self.errors.ensureCursorVisible()
2183 else:
2184 self.errors.insertPlainText(inputTxt)
2185 self.errors.ensureCursorVisible()
2186
2187 self.process.write(strToQByteArray(inputTxt))
2188
2189 self.passwordCheckBox.setChecked(False)
2190 self.input.clear()
2191
2192 def on_input_returnPressed(self):
2193 """
2194 Private slot to handle the press of the return key in the input field.
2195 """
2196 self.intercept = True
2197 self.on_sendButton_clicked()
2198
2199 def keyPressEvent(self, evt):
2200 """
2201 Protected slot to handle a key press event.
2202
2203 @param evt the key press event (QKeyEvent)
2204 """
2205 if self.intercept:
2206 self.intercept = False
2207 evt.accept()
2208 return
2209 super(HgLogBrowserDialog, self).keyPressEvent(evt)
2210
2211 @pyqtSlot() 1954 @pyqtSlot()
2212 def __phaseActTriggered(self): 1955 def __phaseActTriggered(self):
2213 """ 1956 """
2214 Private slot to handle the Change Phase action. 1957 Private slot to handle the Change Phase action.
2215 """ 1958 """

eric ide

mercurial