Plugins/VcsPlugins/vcsMercurial/HgLogBrowserDialog.py

changeset 945
8cd4d08fa9f6
parent 932
efd23a913a09
child 1017
919147f2b518
equal deleted inserted replaced
944:1b59c4ba121e 945:8cd4d08fa9f6
28 COLORNAMES = ["blue", "darkgreen", "red", "green", "darkblue", "purple", 28 COLORNAMES = ["blue", "darkgreen", "red", "green", "darkblue", "purple",
29 "cyan", "olive", "magenta", "darkred", "darkmagenta", 29 "cyan", "olive", "magenta", "darkred", "darkmagenta",
30 "darkcyan", "gray", "yellow"] 30 "darkcyan", "gray", "yellow"]
31 COLORS = [str(QColor(x).name()) for x in COLORNAMES] 31 COLORS = [str(QColor(x).name()) for x in COLORNAMES]
32 32
33
33 class HgLogBrowserDialog(QDialog, Ui_HgLogBrowserDialog): 34 class HgLogBrowserDialog(QDialog, Ui_HgLogBrowserDialog):
34 """ 35 """
35 Class implementing a dialog to browse the log history. 36 Class implementing a dialog to browse the log history.
36 """ 37 """
37 IconColumn = 0 38 IconColumn = 0
38 BranchColumn = 1 39 BranchColumn = 1
39 RevisionColumn = 2 40 RevisionColumn = 2
40 AuthorColumn = 3 41 AuthorColumn = 3
41 DateColumn = 4 42 DateColumn = 4
42 MessageColumn = 5 43 MessageColumn = 5
43 TagsColumn = 6 44 TagsColumn = 6
44 45
45 def __init__(self, vcs, mode = "log", bundle = None, parent = None): 46 def __init__(self, vcs, mode="log", bundle=None, parent=None):
46 """ 47 """
47 Constructor 48 Constructor
48 49
49 @param vcs reference to the vcs object 50 @param vcs reference to the vcs object
50 @param mode mode of the dialog (string; one of log, incoming, outgoing) 51 @param mode mode of the dialog (string; one of log, incoming, outgoing)
94 self.nextButton.setEnabled(False) 95 self.nextButton.setEnabled(False)
95 self.limitSpinBox.setEnabled(False) 96 self.limitSpinBox.setEnabled(False)
96 97
97 self.__messageRole = Qt.UserRole 98 self.__messageRole = Qt.UserRole
98 self.__changesRole = Qt.UserRole + 1 99 self.__changesRole = Qt.UserRole + 1
99 self.__edgesRole = Qt.UserRole + 2 100 self.__edgesRole = Qt.UserRole + 2
100 self.__parentsRole = Qt.UserRole + 3 101 self.__parentsRole = Qt.UserRole + 3
101 102
102 self.process = QProcess() 103 self.process = QProcess()
103 self.process.finished.connect(self.__procFinished) 104 self.process.finished.connect(self.__procFinished)
104 self.process.readyReadStandardOutput.connect(self.__readStdout) 105 self.process.readyReadStandardOutput.connect(self.__readStdout)
105 self.process.readyReadStandardError.connect(self.__readStderr) 106 self.process.readyReadStandardError.connect(self.__readStderr)
106 107
107 self.flags = { 108 self.flags = {
108 'A' : self.trUtf8('Added'), 109 'A': self.trUtf8('Added'),
109 'D' : self.trUtf8('Deleted'), 110 'D': self.trUtf8('Deleted'),
110 'M' : self.trUtf8('Modified'), 111 'M': self.trUtf8('Modified'),
111 } 112 }
112 113
113 self.buf = [] # buffer for stdout 114 self.buf = [] # buffer for stdout
114 self.diff = None 115 self.diff = None
115 self.__started = False 116 self.__started = False
163 def __resortFiles(self): 164 def __resortFiles(self):
164 """ 165 """
165 Private method to resort the changed files tree. 166 Private method to resort the changed files tree.
166 """ 167 """
167 sortColumn = self.filesTree.sortColumn() 168 sortColumn = self.filesTree.sortColumn()
168 self.filesTree.sortItems(1, 169 self.filesTree.sortItems(1,
169 self.filesTree.header().sortIndicatorOrder()) 170 self.filesTree.header().sortIndicatorOrder())
170 self.filesTree.sortItems(sortColumn, 171 self.filesTree.sortItems(sortColumn,
171 self.filesTree.header().sortIndicatorOrder()) 172 self.filesTree.header().sortIndicatorOrder())
172 173
173 def __getColor(self, n): 174 def __getColor(self, n):
174 """ 175 """
175 Private method to get the (rotating) name of the color given an index. 176 Private method to get the (rotating) name of the color given an index.
197 198
198 @param rev revision to calculate edge info for (integer) 199 @param rev revision to calculate edge info for (integer)
199 @param parents list of parent revisions (list of integers) 200 @param parents list of parent revisions (list of integers)
200 @return tuple containing the column and color index for 201 @return tuple containing the column and color index for
201 the given node and a list of tuples indicating the edges 202 the given node and a list of tuples indicating the edges
202 between the given node and its parents 203 between the given node and its parents
203 (integer, integer, [(integer, integer, integer), ...]) 204 (integer, integer, [(integer, integer, integer), ...])
204 """ 205 """
205 if rev not in self.__revs: 206 if rev not in self.__revs:
206 # new head 207 # new head
207 self.__revs.append(rev) 208 self.__revs.append(rev)
245 Private method to generate an icon containing the revision tree for the 246 Private method to generate an icon containing the revision tree for the
246 given data. 247 given data.
247 248
248 @param column column index of the revision (integer) 249 @param column column index of the revision (integer)
249 @param color color of the node (integer) 250 @param color color of the node (integer)
250 @param bottomedges list of edges for the bottom of the node 251 @param bottomedges list of edges for the bottom of the node
251 (list of tuples of three integers) 252 (list of tuples of three integers)
252 @param topedges list of edges for the top of the node 253 @param topedges list of edges for the top of the node
253 (list of tuples of three integers) 254 (list of tuples of three integers)
254 @param dotColor color to be used for the dot (QColor) 255 @param dotColor color to be used for the dot (QColor)
255 @param currentRev flag indicating to draw the icon for the 256 @param currentRev flag indicating to draw the icon for the
256 current revision (boolean) 257 current revision (boolean)
257 @param closed flag indicating to draw an icon for a closed 258 @param closed flag indicating to draw an icon for a closed
320 painter.drawRect(dot_x - 2, dot_y + 1, 321 painter.drawRect(dot_x - 2, dot_y + 1,
321 radius + 4, radius - 2) 322 radius + 4, radius - 2)
322 elif self.commandMode in ("incoming", "outgoing"): 323 elif self.commandMode in ("incoming", "outgoing"):
323 offset = radius // 2 324 offset = radius // 2
324 painter.drawConvexPolygon( 325 painter.drawConvexPolygon(
325 QPoint(dot_x + offset, dot_y), 326 QPoint(dot_x + offset, dot_y),
326 QPoint(dot_x, dot_y + offset), 327 QPoint(dot_x, dot_y + offset),
327 QPoint(dot_x + offset, dot_y + 2 * offset), 328 QPoint(dot_x + offset, dot_y + 2 * offset),
328 QPoint(dot_x + 2 * offset, dot_y + offset) 329 QPoint(dot_x + 2 * offset, dot_y + offset)
329 ) 330 )
330 else: 331 else:
331 painter.drawEllipse(dot_x, dot_y, radius, radius) 332 painter.drawEllipse(dot_x, dot_y, radius, radius)
332 painter.end() 333 painter.end()
365 procStarted = process.waitForStarted() 366 procStarted = process.waitForStarted()
366 if procStarted: 367 if procStarted:
367 finished = process.waitForFinished(30000) 368 finished = process.waitForFinished(30000)
368 if finished and process.exitCode() == 0: 369 if finished and process.exitCode() == 0:
369 output = \ 370 output = \
370 str(process.readAllStandardOutput(), 371 str(process.readAllStandardOutput(),
371 Preferences.getSystem("IOEncoding"), 372 Preferences.getSystem("IOEncoding"),
372 'replace') 373 'replace')
373 parents = [int(p) for p in output.strip().splitlines()] 374 parents = [int(p) for p in output.strip().splitlines()]
374 else: 375 else:
375 if not finished: 376 if not finished:
376 errMsg = self.trUtf8( 377 errMsg = self.trUtf8(
401 procStarted = process.waitForStarted() 402 procStarted = process.waitForStarted()
402 if procStarted: 403 if procStarted:
403 finished = process.waitForFinished(30000) 404 finished = process.waitForFinished(30000)
404 if finished and process.exitCode() == 0: 405 if finished and process.exitCode() == 0:
405 output = \ 406 output = \
406 str(process.readAllStandardOutput(), 407 str(process.readAllStandardOutput(),
407 Preferences.getSystem("IOEncoding"), 408 Preferences.getSystem("IOEncoding"),
408 'replace') 409 'replace')
409 self.__projectRevision = output.strip() 410 self.__projectRevision = output.strip()
410 if self.__projectRevision.endswith("+"): 411 if self.__projectRevision.endswith("+"):
411 self.__projectRevision = self.__projectRevision[:-1] 412 self.__projectRevision = self.__projectRevision[:-1]
412 else: 413 else:
438 procStarted = process.waitForStarted() 439 procStarted = process.waitForStarted()
439 if procStarted: 440 if procStarted:
440 finished = process.waitForFinished(30000) 441 finished = process.waitForFinished(30000)
441 if finished and process.exitCode() == 0: 442 if finished and process.exitCode() == 0:
442 output = \ 443 output = \
443 str(process.readAllStandardOutput(), 444 str(process.readAllStandardOutput(),
444 Preferences.getSystem("IOEncoding"), 445 Preferences.getSystem("IOEncoding"),
445 'replace') 446 'replace')
446 for line in output.splitlines(): 447 for line in output.splitlines():
447 if line.strip().endswith("(closed)"): 448 if line.strip().endswith("(closed)"):
448 parts = line.split() 449 parts = line.split()
449 self.__closedBranchesRevs.append( 450 self.__closedBranchesRevs.append(
487 closedStr = "" 488 closedStr = ""
488 msgtxt = msg[0] 489 msgtxt = msg[0]
489 if len(msgtxt) > 30: 490 if len(msgtxt) > 30:
490 msgtxt = "{0}...".format(msgtxt[:30]) 491 msgtxt = "{0}...".format(msgtxt[:30])
491 itm = QTreeWidgetItem(self.logTree, [ 492 itm = QTreeWidgetItem(self.logTree, [
492 "", 493 "",
493 branches[0] + closedStr, 494 branches[0] + closedStr,
494 "{0:>7}:{1}".format(rev, node), 495 "{0:>7}:{1}".format(rev, node),
495 author, 496 author,
496 date, 497 date,
497 msgtxt, 498 msgtxt,
498 ", ".join(tags), 499 ", ".join(tags),
499 ]) 500 ])
500 501
501 itm.setForeground(self.BranchColumn, 502 itm.setForeground(self.BranchColumn,
502 QBrush(QColor(self.__branchColor(branches[0])))) 503 QBrush(QColor(self.__branchColor(branches[0]))))
503 504
504 if not self.projectMode: 505 if not self.projectMode:
505 parents = self.__getParents(rev) 506 parents = self.__getParents(rev)
506 if not parents: 507 if not parents:
518 self.logTree.indexOfTopLevelItem(itm) - 1)\ 519 self.logTree.indexOfTopLevelItem(itm) - 1)\
519 .data(0, self.__edgesRole) 520 .data(0, self.__edgesRole)
520 else: 521 else:
521 topedges = None 522 topedges = None
522 523
523 icon = self.__generateIcon(column, color, edges, topedges, 524 icon = self.__generateIcon(column, color, edges, topedges,
524 QColor(self.__branchColor(branches[0])), 525 QColor(self.__branchColor(branches[0])),
525 rev == self.__projectRevision, 526 rev == self.__projectRevision,
526 rev in self.__closedBranchesRevs) 527 rev in self.__closedBranchesRevs)
527 itm.setIcon(0, icon) 528 itm.setIcon(0, icon)
528 529
529 try: 530 try:
530 self.__lastRev = int(revision.split(":")[0]) 531 self.__lastRev = int(revision.split(":")[0])
541 @param path path of the file in the repository (string) 542 @param path path of the file in the repository (string)
542 @param copyfrom path the file was copied from (string) 543 @param copyfrom path the file was copied from (string)
543 @return reference to the generated item (QTreeWidgetItem) 544 @return reference to the generated item (QTreeWidgetItem)
544 """ 545 """
545 itm = QTreeWidgetItem(self.filesTree, [ 546 itm = QTreeWidgetItem(self.filesTree, [
546 self.flags[action], 547 self.flags[action],
547 path, 548 path,
548 copyfrom, 549 copyfrom,
549 ]) 550 ])
550 551
551 return itm 552 return itm
552 553
553 def __getLogEntries(self, startRev = None): 554 def __getLogEntries(self, startRev=None):
554 """ 555 """
555 Private method to retrieve log entries from the repository. 556 Private method to retrieve log entries from the repository.
556 557
557 @param startRev revision number to start from (integer, string) 558 @param startRev revision number to start from (integer, string)
558 """ 559 """
592 not self.stopCheckBox.isChecked(): 593 not self.stopCheckBox.isChecked():
593 args.append('--follow') 594 args.append('--follow')
594 if self.commandMode == "log": 595 if self.commandMode == "log":
595 args.append('--copies') 596 args.append('--copies')
596 args.append('--style') 597 args.append('--style')
597 args.append(os.path.join(os.path.dirname(__file__), 598 args.append(os.path.join(os.path.dirname(__file__),
598 "styles", "logBrowser.style")) 599 "styles", "logBrowser.style"))
599 if self.commandMode == "incoming": 600 if self.commandMode == "incoming":
600 if self.bundle: 601 if self.bundle:
601 args.append(self.bundle) 602 args.append(self.bundle)
602 else: 603 else:
684 def __processBuffer(self): 685 def __processBuffer(self):
685 """ 686 """
686 Private method to process the buffered output of the hg log command. 687 Private method to process the buffered output of the hg log command.
687 """ 688 """
688 noEntries = 0 689 noEntries = 0
689 log = {"message" : []} 690 log = {"message": []}
690 changedPaths = [] 691 changedPaths = []
691 initialText = True 692 initialText = True
692 fileCopies = {} 693 fileCopies = {}
693 for s in self.buf: 694 for s in self.buf:
694 if s != "@@@\n": 695 if s != "@@@\n":
702 log["revision"] = value.strip() 703 log["revision"] = value.strip()
703 elif key == "user": 704 elif key == "user":
704 log["author"] = value.strip() 705 log["author"] = value.strip()
705 elif key == "parents": 706 elif key == "parents":
706 log["parents"] = \ 707 log["parents"] = \
707 [int(x.split(":", 1)[0]) 708 [int(x.split(":", 1)[0])
708 for x in value.strip().split()] 709 for x in value.strip().split()]
709 elif key == "date": 710 elif key == "date":
710 log["date"] = " ".join(value.strip().split()[:2]) 711 log["date"] = " ".join(value.strip().split()[:2])
711 elif key == "description": 712 elif key == "description":
712 log["message"].append(value.strip()) 713 log["message"].append(value.strip())
713 elif key == "file_adds": 714 elif key == "file_adds":
714 if value.strip(): 715 if value.strip():
715 for f in value.strip().split(", "): 716 for f in value.strip().split(", "):
716 if f in fileCopies: 717 if f in fileCopies:
717 changedPaths.append({ 718 changedPaths.append({
718 "action" : "A", 719 "action": "A",
719 "path" : f, 720 "path": f,
720 "copyfrom" : fileCopies[f], 721 "copyfrom": fileCopies[f],
721 }) 722 })
722 else: 723 else:
723 changedPaths.append({ 724 changedPaths.append({
724 "action" : "A", 725 "action": "A",
725 "path" : f, 726 "path": f,
726 "copyfrom" : "", 727 "copyfrom": "",
727 }) 728 })
728 elif key == "files_mods": 729 elif key == "files_mods":
729 if value.strip(): 730 if value.strip():
730 for f in value.strip().split(", "): 731 for f in value.strip().split(", "):
731 changedPaths.append({ 732 changedPaths.append({
732 "action" : "M", 733 "action": "M",
733 "path" : f, 734 "path": f,
734 "copyfrom" : "", 735 "copyfrom": "",
735 }) 736 })
736 elif key == "file_dels": 737 elif key == "file_dels":
737 if value.strip(): 738 if value.strip():
738 for f in value.strip().split(", "): 739 for f in value.strip().split(", "):
739 changedPaths.append({ 740 changedPaths.append({
740 "action" : "D", 741 "action": "D",
741 "path" : f, 742 "path": f,
742 "copyfrom" : "", 743 "copyfrom": "",
743 }) 744 })
744 elif key == "file_copies": 745 elif key == "file_copies":
745 if value.strip(): 746 if value.strip():
746 for entry in value.strip().split(", "): 747 for entry in value.strip().split(", "):
747 newName, oldName = entry[:-1].split(" (") 748 newName, oldName = entry[:-1].split(" (")
758 continue 759 continue
759 if value.strip(): 760 if value.strip():
760 log["message"].append(value.strip()) 761 log["message"].append(value.strip())
761 else: 762 else:
762 if len(log) > 1: 763 if len(log) > 1:
763 self.__generateLogItem(log["author"], log["date"], 764 self.__generateLogItem(log["author"], log["date"],
764 log["message"], log["revision"], changedPaths, 765 log["message"], log["revision"], changedPaths,
765 log["parents"], log["branches"], log["tags"]) 766 log["parents"], log["branches"], log["tags"])
766 dt = QDate.fromString(log["date"], Qt.ISODate) 767 dt = QDate.fromString(log["date"], Qt.ISODate)
767 if not self.__maxDate.isValid() and \ 768 if not self.__maxDate.isValid() and \
768 not self.__minDate.isValid(): 769 not self.__minDate.isValid():
772 if self.__maxDate < dt: 773 if self.__maxDate < dt:
773 self.__maxDate = dt 774 self.__maxDate = dt
774 if self.__minDate > dt: 775 if self.__minDate > dt:
775 self.__minDate = dt 776 self.__minDate = dt
776 noEntries += 1 777 noEntries += 1
777 log = {"message" : []} 778 log = {"message": []}
778 changedPaths = [] 779 changedPaths = []
779 fileCopies = {} 780 fileCopies = {}
780 781
781 self.logTree.doItemsLayout() 782 self.logTree.doItemsLayout()
782 self.__resizeColumnsLog() 783 self.__resizeColumnsLog()
816 self.__filterLogsEnabled = True 817 self.__filterLogsEnabled = True
817 self.__filterLogs() 818 self.__filterLogs()
818 819
819 def __readStdout(self): 820 def __readStdout(self):
820 """ 821 """
821 Private slot to handle the readyReadStandardOutput signal. 822 Private slot to handle the readyReadStandardOutput signal.
822 823
823 It reads the output of the process and inserts it into a buffer. 824 It reads the output of the process and inserts it into a buffer.
824 """ 825 """
825 self.process.setReadChannel(QProcess.StandardOutput) 826 self.process.setReadChannel(QProcess.StandardOutput)
826 827
827 while self.process.canReadLine(): 828 while self.process.canReadLine():
828 line = str(self.process.readLine(), 829 line = str(self.process.readLine(),
829 Preferences.getSystem("IOEncoding"), 830 Preferences.getSystem("IOEncoding"),
830 'replace') 831 'replace')
831 self.buf.append(line) 832 self.buf.append(line)
832 833
833 def __readStderr(self): 834 def __readStderr(self):
834 """ 835 """
837 It reads the error output of the process and inserts it into the 838 It reads the error output of the process and inserts it into the
838 error pane. 839 error pane.
839 """ 840 """
840 if self.process is not None: 841 if self.process is not None:
841 self.errorGroup.show() 842 self.errorGroup.show()
842 s = str(self.process.readAllStandardError(), 843 s = str(self.process.readAllStandardError(),
843 Preferences.getSystem("IOEncoding"), 844 Preferences.getSystem("IOEncoding"),
844 'replace') 845 'replace')
845 self.errors.insertPlainText(s) 846 self.errors.insertPlainText(s)
846 self.errors.ensureCursorVisible() 847 self.errors.ensureCursorVisible()
847 848
848 def __diffRevisions(self, rev1, rev2): 849 def __diffRevisions(self, rev1, rev2):
1082 @pyqtSlot(bool) 1083 @pyqtSlot(bool)
1083 def on_stopCheckBox_clicked(self, checked): 1084 def on_stopCheckBox_clicked(self, checked):
1084 """ 1085 """
1085 Private slot called, when the stop on copy/move checkbox is clicked 1086 Private slot called, when the stop on copy/move checkbox is clicked
1086 """ 1087 """
1087 self.vcs.getPlugin().setPreferences("StopLogOnCopy", 1088 self.vcs.getPlugin().setPreferences("StopLogOnCopy",
1088 self.stopCheckBox.isChecked()) 1089 self.stopCheckBox.isChecked())
1089 self.nextButton.setEnabled(True) 1090 self.nextButton.setEnabled(True)
1090 self.limitSpinBox.setEnabled(True) 1091 self.limitSpinBox.setEnabled(True)
1091 1092
1092 def on_passwordCheckBox_toggled(self, isOn): 1093 def on_passwordCheckBox_toggled(self, isOn):

eric ide

mercurial