Plugins/VcsPlugins/vcsMercurial/HgLogBrowserDialog.py

branch
Py2 comp.
changeset 3484
645c12de6b0c
parent 3178
f25fc1364c88
parent 3359
6b6c224d67d6
child 3532
86ac124f322c
equal deleted inserted replaced
3456:96232974dcdb 3484:645c12de6b0c
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 from __future__ import unicode_literals 10 from __future__ import unicode_literals
11 try: 11 try:
12 str = unicode # __IGNORE_WARNING__ 12 str = unicode
13 except (NameError): 13 except NameError:
14 pass 14 pass
15 15
16 import os 16 import os
17 import re
17 18
18 from PyQt4.QtCore import pyqtSlot, Qt, QDate, QProcess, QTimer, QRegExp, \ 19 from PyQt4.QtCore import pyqtSlot, Qt, QDate, QProcess, QTimer, QRegExp, \
19 QSize, QPoint 20 QSize, QPoint
20 from PyQt4.QtGui import QDialog, QDialogButtonBox, QHeaderView, \ 21 from PyQt4.QtGui import QWidget, QDialogButtonBox, QHeaderView, \
21 QTreeWidgetItem, QApplication, QCursor, QLineEdit, QColor, \ 22 QTreeWidgetItem, QApplication, QCursor, QLineEdit, QColor, \
22 QPixmap, QPainter, QPen, QBrush, QIcon 23 QPixmap, QPainter, QPen, QBrush, QIcon, QMenu
23 24
24 from E5Gui.E5Application import e5App 25 from E5Gui.E5Application import e5App
25 from E5Gui import E5MessageBox 26 from E5Gui import E5MessageBox
26 27
27 from .Ui_HgLogBrowserDialog import Ui_HgLogBrowserDialog 28 from .Ui_HgLogBrowserDialog import Ui_HgLogBrowserDialog
28 29
29 import Preferences 30 import UI.PixmapCache
30 31
31 COLORNAMES = ["blue", "darkgreen", "red", "green", "darkblue", "purple", 32 COLORNAMES = ["blue", "darkgreen", "red", "green", "darkblue", "purple",
32 "cyan", "olive", "magenta", "darkred", "darkmagenta", 33 "cyan", "olive", "magenta", "darkred", "darkmagenta",
33 "darkcyan", "gray", "yellow"] 34 "darkcyan", "gray", "yellow"]
34 COLORS = [str(QColor(x).name()) for x in COLORNAMES] 35 COLORS = [str(QColor(x).name()) for x in COLORNAMES]
35 36
36 37
37 class HgLogBrowserDialog(QDialog, Ui_HgLogBrowserDialog): 38 class HgLogBrowserDialog(QWidget, Ui_HgLogBrowserDialog):
38 """ 39 """
39 Class implementing a dialog to browse the log history. 40 Class implementing a dialog to browse the log history.
40 """ 41 """
41 IconColumn = 0 42 IconColumn = 0
42 BranchColumn = 1 43 BranchColumn = 1
45 AuthorColumn = 4 46 AuthorColumn = 4
46 DateColumn = 5 47 DateColumn = 5
47 MessageColumn = 6 48 MessageColumn = 6
48 TagsColumn = 7 49 TagsColumn = 7
49 50
50 def __init__(self, vcs, mode="log", bundle=None, isFile=False, 51 LargefilesCacheL = ".hglf/"
51 parent=None): 52 LargefilesCacheW = ".hglf\\"
53 PathSeparatorRe = re.compile(r"/|\\")
54
55 def __init__(self, vcs, mode="log", parent=None):
52 """ 56 """
53 Constructor 57 Constructor
54 58
55 @param vcs reference to the vcs object 59 @param vcs reference to the vcs object
56 @param mode mode of the dialog (string; one of log, incoming, outgoing) 60 @param mode mode of the dialog (string; one of log, incoming, outgoing)
57 @param bundle name of a bundle file (string)
58 @param isFile flag indicating log for a file is to be shown (boolean)
59 @param parent parent widget (QWidget) 61 @param parent parent widget (QWidget)
60 """ 62 """
61 super(HgLogBrowserDialog, self).__init__(parent) 63 super(HgLogBrowserDialog, self).__init__(parent)
62 self.setupUi(self) 64 self.setupUi(self)
63 65
66 self.__position = QPoint()
67
64 if mode == "log": 68 if mode == "log":
65 self.setWindowTitle(self.trUtf8("Mercurial Log")) 69 self.setWindowTitle(self.tr("Mercurial Log"))
66 elif mode == "incoming": 70 elif mode == "incoming":
67 self.setWindowTitle(self.trUtf8("Mercurial Log (Incoming)")) 71 self.setWindowTitle(self.tr("Mercurial Log (Incoming)"))
68 elif mode == "outgoing": 72 elif mode == "outgoing":
69 self.setWindowTitle(self.trUtf8("Mercurial Log (Outgoing)")) 73 self.setWindowTitle(self.tr("Mercurial Log (Outgoing)"))
70 74
71 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) 75 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
72 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) 76 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
73 77
74 self.filesTree.headerItem().setText(self.filesTree.columnCount(), "") 78 self.filesTree.headerItem().setText(self.filesTree.columnCount(), "")
75 self.filesTree.header().setSortIndicator(0, Qt.AscendingOrder) 79 self.filesTree.header().setSortIndicator(0, Qt.AscendingOrder)
76 80
77 self.refreshButton = self.buttonBox.addButton( 81 self.refreshButton = self.buttonBox.addButton(
78 self.trUtf8("&Refresh"), QDialogButtonBox.ActionRole) 82 self.tr("&Refresh"), QDialogButtonBox.ActionRole)
79 self.refreshButton.setToolTip( 83 self.refreshButton.setToolTip(
80 self.trUtf8("Press to refresh the list of changesets")) 84 self.tr("Press to refresh the list of changesets"))
81 self.refreshButton.setEnabled(False) 85 self.refreshButton.setEnabled(False)
82
83 self.sbsCheckBox.setEnabled(isFile)
84 self.sbsCheckBox.setVisible(isFile)
85 86
86 self.vcs = vcs 87 self.vcs = vcs
87 if mode in ("log", "incoming", "outgoing"): 88 if mode in ("log", "incoming", "outgoing"):
88 self.commandMode = mode 89 self.commandMode = mode
89 self.initialCommandMode = mode 90 self.initialCommandMode = mode
90 else: 91 else:
91 self.commandMode = "log" 92 self.commandMode = "log"
92 self.bundle = bundle 93 self.initialCommandMode = "log"
93 self.__hgClient = vcs.getClient() 94 self.__hgClient = vcs.getClient()
94 95
96 self.__bundle = ""
97 self.__filename = ""
98 self.__isFile = False
99
95 self.__initData() 100 self.__initData()
96 101
97 self.__allBranchesFilter = self.trUtf8("All") 102 self.__allBranchesFilter = self.tr("All")
98 103
99 self.fromDate.setDisplayFormat("yyyy-MM-dd") 104 self.fromDate.setDisplayFormat("yyyy-MM-dd")
100 self.toDate.setDisplayFormat("yyyy-MM-dd") 105 self.toDate.setDisplayFormat("yyyy-MM-dd")
101 self.fromDate.setDate(QDate.currentDate()) 106 self.__resetUI()
102 self.toDate.setDate(QDate.currentDate())
103 self.fieldCombo.setCurrentIndex(self.fieldCombo.findText(
104 self.trUtf8("Message")))
105 self.limitSpinBox.setValue(self.vcs.getPlugin().getPreferences(
106 "LogLimit"))
107 self.stopCheckBox.setChecked(self.vcs.getPlugin().getPreferences(
108 "StopLogOnCopy"))
109
110 if mode in ("incoming", "outgoing"):
111 self.nextButton.setEnabled(False)
112 self.limitSpinBox.setEnabled(False)
113 107
114 self.__messageRole = Qt.UserRole 108 self.__messageRole = Qt.UserRole
115 self.__changesRole = Qt.UserRole + 1 109 self.__changesRole = Qt.UserRole + 1
116 self.__edgesRole = Qt.UserRole + 2 110 self.__edgesRole = Qt.UserRole + 2
117 self.__parentsRole = Qt.UserRole + 3 111 self.__parentsRole = Qt.UserRole + 3
123 self.process.finished.connect(self.__procFinished) 117 self.process.finished.connect(self.__procFinished)
124 self.process.readyReadStandardOutput.connect(self.__readStdout) 118 self.process.readyReadStandardOutput.connect(self.__readStdout)
125 self.process.readyReadStandardError.connect(self.__readStderr) 119 self.process.readyReadStandardError.connect(self.__readStderr)
126 120
127 self.flags = { 121 self.flags = {
128 'A': self.trUtf8('Added'), 122 'A': self.tr('Added'),
129 'D': self.trUtf8('Deleted'), 123 'D': self.tr('Deleted'),
130 'M': self.trUtf8('Modified'), 124 'M': self.tr('Modified'),
131 } 125 }
132 126
133 self.__dotRadius = 8 127 self.__dotRadius = 8
134 self.__rowHeight = 20 128 self.__rowHeight = 20
135 129
136 self.logTree.setIconSize( 130 self.logTree.setIconSize(
137 QSize(100 * self.__rowHeight, self.__rowHeight)) 131 QSize(100 * self.__rowHeight, self.__rowHeight))
138 if self.vcs.version >= (1, 8): 132 if self.vcs.version >= (1, 8):
139 self.logTree.headerItem().setText( 133 self.logTree.headerItem().setText(
140 self.logTree.columnCount(), self.trUtf8("Bookmarks")) 134 self.logTree.columnCount(), self.tr("Bookmarks"))
141 if self.vcs.version < (2, 1): 135 if self.vcs.version < (2, 1):
142 self.logTree.setColumnHidden(self.PhaseColumn, True) 136 self.logTree.setColumnHidden(self.PhaseColumn, True)
143 self.phaseLine.hide() 137
144 self.phaseButton.hide() 138 self.__actionsMenu = QMenu()
145 if self.vcs.version < (2, 0): 139 if self.vcs.version >= (2, 0):
146 self.graftButton.setEnabled(False) 140 self.__graftAct = self.__actionsMenu.addAction(
147 self.graftButton.hide() 141 self.tr("Copy Changesets"), self.__graftActTriggered)
148 if self.phaseButton.isHidden() and \ 142 self.__graftAct.setToolTip(self.tr(
149 self.graftButton.isHidden(): 143 "Copy the selected changesets to the current branch"))
150 self.phaseLine.hide() 144 else:
145 self.__graftAct = None
146
147 if self.vcs.version >= (2, 1):
148 self.__phaseAct = self.__actionsMenu.addAction(
149 self.tr("Change Phase"), self.__phaseActTriggered)
150 self.__phaseAct.setToolTip(self.tr(
151 "Change the phase of the selected revisions"))
152 self.__phaseAct.setWhatsThis(self.tr(
153 """<b>Change Phase</b>\n<p>This changes the phase of the"""
154 """ selected revisions. The selected revisions have to have"""
155 """ the same current phase.</p>"""))
156 else:
157 self.__phaseAct = None
158
159 self.__tagAct = self.__actionsMenu.addAction(
160 self.tr("Tag"), self.__tagActTriggered)
161 self.__tagAct.setToolTip(self.tr("Tag the selected revision"))
162
163 self.__switchAct = self.__actionsMenu.addAction(
164 self.tr("Switch"), self.__switchActTriggered)
165 self.__switchAct.setToolTip(self.tr(
166 "Switch the working directory to the selected revision"))
167
168 if self.vcs.version >= (2, 0):
169 self.__lfPullAct = self.__actionsMenu.addAction(
170 self.tr("Pull Large Files"), self.__lfPullActTriggered)
171 self.__lfPullAct.setToolTip(self.tr(
172 "Pull large files for selected revisions"))
173 else:
174 self.__lfPullAct = None
175
176 self.actionsButton.setIcon(
177 UI.PixmapCache.getIcon("actionsToolButton.png"))
178 self.actionsButton.setMenu(self.__actionsMenu)
151 179
152 def __initData(self): 180 def __initData(self):
153 """ 181 """
154 Private method to (re-)initialize some data. 182 Private method to (re-)initialize some data.
155 """ 183 """
187 self.process.state() != QProcess.NotRunning: 215 self.process.state() != QProcess.NotRunning:
188 self.process.terminate() 216 self.process.terminate()
189 QTimer.singleShot(2000, self.process.kill) 217 QTimer.singleShot(2000, self.process.kill)
190 self.process.waitForFinished(3000) 218 self.process.waitForFinished(3000)
191 219
220 self.__position = self.pos()
221
192 e.accept() 222 e.accept()
223
224 def show(self):
225 """
226 Public slot to show the dialog.
227 """
228 if not self.__position.isNull():
229 self.move(self.__position)
230 self.__resetUI()
231
232 super(HgLogBrowserDialog, self).show()
233
234 def __resetUI(self):
235 """
236 Private method to reset the user interface.
237 """
238 self.branchCombo.clear()
239 self.fromDate.setDate(QDate.currentDate())
240 self.toDate.setDate(QDate.currentDate())
241 self.fieldCombo.setCurrentIndex(self.fieldCombo.findText(
242 self.tr("Message")))
243 self.limitSpinBox.setValue(self.vcs.getPlugin().getPreferences(
244 "LogLimit"))
245 self.stopCheckBox.setChecked(self.vcs.getPlugin().getPreferences(
246 "StopLogOnCopy"))
247
248 if self.initialCommandMode in ("incoming", "outgoing"):
249 self.nextButton.setEnabled(False)
250 self.limitSpinBox.setEnabled(False)
251 else:
252 self.nextButton.setEnabled(True)
253 self.limitSpinBox.setEnabled(True)
254
255 self.logTree.clear()
256
257 self.commandMode = self.initialCommandMode
193 258
194 def __resizeColumnsLog(self): 259 def __resizeColumnsLog(self):
195 """ 260 """
196 Private method to resize the log tree columns. 261 Private method to resize the log tree columns.
197 """ 262 """
388 """ 453 """
389 errMsg = "" 454 errMsg = ""
390 parents = [-1] 455 parents = [-1]
391 456
392 if int(rev) > 0: 457 if int(rev) > 0:
393 args = [] 458 args = self.vcs.initCommand("parents")
394 args.append("parents")
395 if self.commandMode == "incoming": 459 if self.commandMode == "incoming":
396 if self.bundle: 460 if self.__bundle:
397 args.append("--repository") 461 args.append("--repository")
398 args.append(self.bundle) 462 args.append(self.__bundle)
399 elif self.vcs.bundleFile and \ 463 elif self.vcs.bundleFile and \
400 os.path.exists(self.vcs.bundleFile): 464 os.path.exists(self.vcs.bundleFile):
401 args.append("--repository") 465 args.append("--repository")
402 args.append(self.vcs.bundleFile) 466 args.append(self.vcs.bundleFile)
403 args.append("--template") 467 args.append("--template")
404 args.append("{rev}\n") 468 args.append("{rev}\n")
405 args.append("-r") 469 args.append("-r")
406 args.append(rev) 470 args.append(rev)
407 if not self.projectMode: 471 if not self.projectMode:
408 args.append(self.filename) 472 args.append(self.__filename)
409 473
410 output = "" 474 output = ""
411 if self.__hgClient: 475 if self.__hgClient:
412 output, errMsg = self.__hgClient.runcommand(args) 476 output, errMsg = self.__hgClient.runcommand(args)
413 else: 477 else:
416 process.start('hg', args) 480 process.start('hg', args)
417 procStarted = process.waitForStarted(5000) 481 procStarted = process.waitForStarted(5000)
418 if procStarted: 482 if procStarted:
419 finished = process.waitForFinished(30000) 483 finished = process.waitForFinished(30000)
420 if finished and process.exitCode() == 0: 484 if finished and process.exitCode() == 0:
421 output = \ 485 output = str(process.readAllStandardOutput(),
422 str(process.readAllStandardOutput(), 486 self.vcs.getEncoding(), 'replace')
423 Preferences.getSystem("IOEncoding"),
424 'replace')
425 else: 487 else:
426 if not finished: 488 if not finished:
427 errMsg = self.trUtf8( 489 errMsg = self.tr(
428 "The hg process did not finish within 30s.") 490 "The hg process did not finish within 30s.")
429 else: 491 else:
430 errMsg = self.trUtf8("Could not start the hg executable.") 492 errMsg = self.tr("Could not start the hg executable.")
431 493
432 if errMsg: 494 if errMsg:
433 E5MessageBox.critical( 495 E5MessageBox.critical(
434 self, 496 self,
435 self.trUtf8("Mercurial Error"), 497 self.tr("Mercurial Error"),
436 errMsg) 498 errMsg)
437 499
438 if output: 500 if output:
439 parents = [int(p) for p in output.strip().splitlines()] 501 parents = [int(p) for p in output.strip().splitlines()]
440 502
444 """ 506 """
445 Private method to determine the revision of the project directory. 507 Private method to determine the revision of the project directory.
446 """ 508 """
447 errMsg = "" 509 errMsg = ""
448 510
449 args = [] 511 args = self.vcs.initCommand("identify")
450 args.append("identify")
451 args.append("-nb") 512 args.append("-nb")
452 513
453 output = "" 514 output = ""
454 if self.__hgClient: 515 if self.__hgClient:
455 output, errMsg = self.__hgClient.runcommand(args) 516 output, errMsg = self.__hgClient.runcommand(args)
459 process.start('hg', args) 520 process.start('hg', args)
460 procStarted = process.waitForStarted(5000) 521 procStarted = process.waitForStarted(5000)
461 if procStarted: 522 if procStarted:
462 finished = process.waitForFinished(30000) 523 finished = process.waitForFinished(30000)
463 if finished and process.exitCode() == 0: 524 if finished and process.exitCode() == 0:
464 output = \ 525 output = str(process.readAllStandardOutput(),
465 str(process.readAllStandardOutput(), 526 self.vcs.getEncoding(), 'replace')
466 Preferences.getSystem("IOEncoding"),
467 'replace')
468 else: 527 else:
469 if not finished: 528 if not finished:
470 errMsg = self.trUtf8( 529 errMsg = self.tr(
471 "The hg process did not finish within 30s.") 530 "The hg process did not finish within 30s.")
472 else: 531 else:
473 errMsg = self.trUtf8("Could not start the hg executable.") 532 errMsg = self.tr("Could not start the hg executable.")
474 533
475 if errMsg: 534 if errMsg:
476 E5MessageBox.critical( 535 E5MessageBox.critical(
477 self, 536 self,
478 self.trUtf8("Mercurial Error"), 537 self.tr("Mercurial Error"),
479 errMsg) 538 errMsg)
480 539
481 if output: 540 if output:
482 outputList = output.strip().split(None, 1) 541 outputList = output.strip().split(None, 1)
483 if len(outputList) == 2: 542 if len(outputList) == 2:
491 Private method to get the list of closed branches. 550 Private method to get the list of closed branches.
492 """ 551 """
493 self.__closedBranchesRevs = [] 552 self.__closedBranchesRevs = []
494 errMsg = "" 553 errMsg = ""
495 554
496 args = [] 555 args = self.vcs.initCommand("branches")
497 args.append("branches")
498 args.append("--closed") 556 args.append("--closed")
499 557
500 output = "" 558 output = ""
501 if self.__hgClient: 559 if self.__hgClient:
502 output, errMsg = self.__hgClient.runcommand(args) 560 output, errMsg = self.__hgClient.runcommand(args)
506 process.start('hg', args) 564 process.start('hg', args)
507 procStarted = process.waitForStarted(5000) 565 procStarted = process.waitForStarted(5000)
508 if procStarted: 566 if procStarted:
509 finished = process.waitForFinished(30000) 567 finished = process.waitForFinished(30000)
510 if finished and process.exitCode() == 0: 568 if finished and process.exitCode() == 0:
511 output = \ 569 output = str(process.readAllStandardOutput(),
512 str(process.readAllStandardOutput(), 570 self.vcs.getEncoding(), 'replace')
513 Preferences.getSystem("IOEncoding"),
514 'replace')
515 else: 571 else:
516 if not finished: 572 if not finished:
517 errMsg = self.trUtf8( 573 errMsg = self.tr(
518 "The hg process did not finish within 30s.") 574 "The hg process did not finish within 30s.")
519 else: 575 else:
520 errMsg = self.trUtf8("Could not start the hg executable.") 576 errMsg = self.tr("Could not start the hg executable.")
521 577
522 if errMsg: 578 if errMsg:
523 E5MessageBox.critical( 579 E5MessageBox.critical(
524 self, 580 self,
525 self.trUtf8("Mercurial Error"), 581 self.tr("Mercurial Error"),
526 errMsg) 582 errMsg)
527 583
528 if output: 584 if output:
529 for line in output.splitlines(): 585 for line in output.splitlines():
530 if line.strip().endswith("(closed)"): 586 if line.strip().endswith("(closed)"):
554 for line in message: 610 for line in message:
555 msg.append(line.strip()) 611 msg.append(line.strip())
556 612
557 rev, node = revision.split(":") 613 rev, node = revision.split(":")
558 if rev in self.__closedBranchesRevs: 614 if rev in self.__closedBranchesRevs:
559 closedStr = "--" 615 closedStr = " \u2612"
560 else: 616 else:
561 closedStr = "" 617 closedStr = ""
562 msgtxt = msg[0] 618 msgtxt = msg[0]
563 if len(msgtxt) > 30: 619 if len(msgtxt) > 30:
564 msgtxt = "{0}...".format(msgtxt[:30]) 620 msgtxt = "{0}...".format(msgtxt[:30])
586 column, color, edges = self.__generateEdges(int(rev), parents) 642 column, color, edges = self.__generateEdges(int(rev), parents)
587 643
588 itm.setData(0, self.__messageRole, message) 644 itm.setData(0, self.__messageRole, message)
589 itm.setData(0, self.__changesRole, changedPaths) 645 itm.setData(0, self.__changesRole, changedPaths)
590 itm.setData(0, self.__edgesRole, edges) 646 itm.setData(0, self.__edgesRole, edges)
591 itm.setData(0, self.__parentsRole, parents) 647 if parents == [-1]:
648 itm.setData(0, self.__parentsRole, [])
649 else:
650 itm.setData(0, self.__parentsRole, parents)
592 651
593 if self.logTree.topLevelItemCount() > 1: 652 if self.logTree.topLevelItemCount() > 1:
594 topedges = \ 653 topedges = \
595 self.logTree.topLevelItem( 654 self.logTree.topLevelItem(
596 self.logTree.indexOfTopLevelItem(itm) - 1)\ 655 self.logTree.indexOfTopLevelItem(itm) - 1)\
645 self.buf = [] 704 self.buf = []
646 self.cancelled = False 705 self.cancelled = False
647 self.errors.clear() 706 self.errors.clear()
648 self.intercept = False 707 self.intercept = False
649 708
650 args = [] 709 preargs = []
651 args.append(self.commandMode) 710 args = self.vcs.initCommand(self.commandMode)
652 self.vcs.addArguments(args, self.vcs.options['global'])
653 self.vcs.addArguments(args, self.vcs.options['log'])
654 args.append('--verbose') 711 args.append('--verbose')
655 if self.commandMode not in ("incoming", "outgoing"): 712 if self.commandMode not in ("incoming", "outgoing"):
656 args.append('--limit') 713 args.append('--limit')
657 args.append(str(self.limitSpinBox.value())) 714 args.append(str(self.limitSpinBox.value()))
658 if self.commandMode in ("incoming", "outgoing"): 715 if self.commandMode in ("incoming", "outgoing"):
680 else: 737 else:
681 args.append(os.path.join(os.path.dirname(__file__), 738 args.append(os.path.join(os.path.dirname(__file__),
682 "styles", 739 "styles",
683 "logBrowser.style")) 740 "logBrowser.style"))
684 if self.commandMode == "incoming": 741 if self.commandMode == "incoming":
685 if self.bundle: 742 if self.__bundle:
686 args.append(self.bundle) 743 args.append(self.__bundle)
687 elif not self.vcs.hasSubrepositories(): 744 elif not self.vcs.hasSubrepositories():
688 project = e5App().getObject("Project") 745 project = e5App().getObject("Project")
689 self.vcs.bundleFile = os.path.join( 746 self.vcs.bundleFile = os.path.join(
690 project.getProjectManagementDir(), "hg-bundle.hg") 747 project.getProjectManagementDir(), "hg-bundle.hg")
691 args.append('--bundle') 748 if os.path.exists(self.vcs.bundleFile):
749 os.remove(self.vcs.bundleFile)
750 preargs = args[:]
751 preargs.append("--quiet")
752 preargs.append('--bundle')
753 preargs.append(self.vcs.bundleFile)
692 args.append(self.vcs.bundleFile) 754 args.append(self.vcs.bundleFile)
693 if not self.projectMode: 755 if not self.projectMode:
694 args.append(self.filename) 756 args.append(self.__filename)
695 757
696 if self.__hgClient: 758 if self.__hgClient:
697 self.inputGroup.setEnabled(False) 759 self.inputGroup.setEnabled(False)
698 self.inputGroup.hide() 760 self.inputGroup.hide()
699 761
700 out, err = self.__hgClient.runcommand(args) 762 if preargs:
701 self.buf = out.splitlines(True) 763 out, err = self.__hgClient.runcommand(preargs)
764 else:
765 err = ""
702 if err: 766 if err:
703 self.__showError(err) 767 self.__showError(err)
704 self.__processBuffer() 768 elif self.commandMode != "incoming" or \
769 (self.vcs.bundleFile and
770 os.path.exists(self.vcs.bundleFile)) or \
771 self.__bundle:
772 out, err = self.__hgClient.runcommand(args)
773 self.buf = out.splitlines(True)
774 if err:
775 self.__showError(err)
776 self.__processBuffer()
705 self.__finish() 777 self.__finish()
706 else: 778 else:
707 self.process.kill() 779 self.process.kill()
708 780
709 self.process.setWorkingDirectory(self.repodir) 781 self.process.setWorkingDirectory(self.repodir)
710 782
711 self.inputGroup.setEnabled(True) 783 self.inputGroup.setEnabled(True)
712 self.inputGroup.show() 784 self.inputGroup.show()
713 785
714 self.process.start('hg', args) 786 if preargs:
715 procStarted = self.process.waitForStarted(5000) 787 process = QProcess()
716 if not procStarted: 788 process.setWorkingDirectory(self.repodir)
717 self.inputGroup.setEnabled(False) 789 process.start('hg', args)
718 self.inputGroup.hide() 790 procStarted = process.waitForStarted(5000)
719 E5MessageBox.critical( 791 if procStarted:
720 self, 792 process.waitForFinished(30000)
721 self.trUtf8('Process Generation Error'), 793
722 self.trUtf8( 794 if self.commandMode != "incoming" or \
723 'The process {0} could not be started. ' 795 (self.vcs.bundleFile and
724 'Ensure, that it is in the search path.' 796 os.path.exists(self.vcs.bundleFile)) or \
725 ).format('hg')) 797 self.__bundle:
726 798 self.process.start('hg', args)
727 def start(self, fn): 799 procStarted = self.process.waitForStarted(5000)
800 if not procStarted:
801 self.inputGroup.setEnabled(False)
802 self.inputGroup.hide()
803 E5MessageBox.critical(
804 self,
805 self.tr('Process Generation Error'),
806 self.tr(
807 'The process {0} could not be started. '
808 'Ensure, that it is in the search path.'
809 ).format('hg'))
810 else:
811 self.__finish()
812
813 def start(self, fn, bundle=None, isFile=False):
728 """ 814 """
729 Public slot to start the hg log command. 815 Public slot to start the hg log command.
730 816
731 @param fn filename to show the log for (string) 817 @param fn filename to show the log for (string)
732 """ 818 @keyparam bundle name of a bundle file (string)
819 @keyparam isFile flag indicating log for a file is to be shown
820 (boolean)
821 """
822 self.__bundle = bundle
823 self.__isFile = isFile
824
825 self.sbsCheckBox.setEnabled(isFile)
826 self.sbsCheckBox.setVisible(isFile)
827
733 self.errorGroup.hide() 828 self.errorGroup.hide()
734 QApplication.processEvents() 829 QApplication.processEvents()
735 830
736 self.filename = fn 831 self.__initData()
832
833 self.__filename = fn
737 self.dname, self.fname = self.vcs.splitPath(fn) 834 self.dname, self.fname = self.vcs.splitPath(fn)
738 835
739 # find the root of the repo 836 # find the root of the repo
740 self.repodir = self.dname 837 self.repodir = self.dname
741 while not os.path.isdir(os.path.join(self.repodir, self.vcs.adminDir)): 838 while not os.path.isdir(os.path.join(self.repodir, self.vcs.adminDir)):
782 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) 879 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
783 880
784 self.inputGroup.setEnabled(False) 881 self.inputGroup.setEnabled(False)
785 self.inputGroup.hide() 882 self.inputGroup.hide()
786 self.refreshButton.setEnabled(True) 883 self.refreshButton.setEnabled(True)
884
885 def __modifyForLargeFiles(self, filename):
886 """
887 Private method to convert the displayed file name for a large file.
888
889 @param filename file name to be processed (string)
890 @return processed file name (string)
891 """
892 if filename.startswith((self.LargefilesCacheL, self.LargefilesCacheW)):
893 return self.tr("{0} (large file)").format(
894 self.PathSeparatorRe.split(filename, 1)[1])
895 else:
896 return filename
787 897
788 def __processBuffer(self): 898 def __processBuffer(self):
789 """ 899 """
790 Private method to process the buffered output of the hg log command. 900 Private method to process the buffered output of the hg log command.
791 """ 901 """
818 if value.strip(): 928 if value.strip():
819 for f in value.strip().split(", "): 929 for f in value.strip().split(", "):
820 if f in fileCopies: 930 if f in fileCopies:
821 changedPaths.append({ 931 changedPaths.append({
822 "action": "A", 932 "action": "A",
823 "path": f, 933 "path": self.__modifyForLargeFiles(f),
824 "copyfrom": fileCopies[f], 934 "copyfrom": self.__modifyForLargeFiles(
935 fileCopies[f]),
825 }) 936 })
826 else: 937 else:
827 changedPaths.append({ 938 changedPaths.append({
828 "action": "A", 939 "action": "A",
829 "path": f, 940 "path": self.__modifyForLargeFiles(f),
830 "copyfrom": "", 941 "copyfrom": "",
831 }) 942 })
832 elif key == "files_mods": 943 elif key == "files_mods":
833 if value.strip(): 944 if value.strip():
834 for f in value.strip().split(", "): 945 for f in value.strip().split(", "):
835 changedPaths.append({ 946 changedPaths.append({
836 "action": "M", 947 "action": "M",
837 "path": f, 948 "path": self.__modifyForLargeFiles(f),
838 "copyfrom": "", 949 "copyfrom": "",
839 }) 950 })
840 elif key == "file_dels": 951 elif key == "file_dels":
841 if value.strip(): 952 if value.strip():
842 for f in value.strip().split(", "): 953 for f in value.strip().split(", "):
843 changedPaths.append({ 954 changedPaths.append({
844 "action": "D", 955 "action": "D",
845 "path": f, 956 "path": self.__modifyForLargeFiles(f),
846 "copyfrom": "", 957 "copyfrom": "",
847 }) 958 })
848 elif key == "file_copies": 959 elif key == "file_copies":
849 if value.strip(): 960 if value.strip():
850 for entry in value.strip().split(", "): 961 for entry in value.strip().split(", "):
924 1035
925 self.__filterLogsEnabled = True 1036 self.__filterLogsEnabled = True
926 self.__filterLogs() 1037 self.__filterLogs()
927 1038
928 self.__updateDiffButtons() 1039 self.__updateDiffButtons()
929 self.__updatePhaseButton() 1040 self.__updateToolMenuActions()
930 self.__updateGraftButton()
931 1041
932 def __readStdout(self): 1042 def __readStdout(self):
933 """ 1043 """
934 Private slot to handle the readyReadStandardOutput signal. 1044 Private slot to handle the readyReadStandardOutput signal.
935 1045
936 It reads the output of the process and inserts it into a buffer. 1046 It reads the output of the process and inserts it into a buffer.
937 """ 1047 """
938 self.process.setReadChannel(QProcess.StandardOutput) 1048 self.process.setReadChannel(QProcess.StandardOutput)
939 1049
940 while self.process.canReadLine(): 1050 while self.process.canReadLine():
941 line = str(self.process.readLine(), 1051 line = str(self.process.readLine(), self.vcs.getEncoding(),
942 Preferences.getSystem("IOEncoding"),
943 'replace') 1052 'replace')
944 self.buf.append(line) 1053 self.buf.append(line)
945 1054
946 def __readStderr(self): 1055 def __readStderr(self):
947 """ 1056 """
950 It reads the error output of the process and inserts it into the 1059 It reads the error output of the process and inserts it into the
951 error pane. 1060 error pane.
952 """ 1061 """
953 if self.process is not None: 1062 if self.process is not None:
954 s = str(self.process.readAllStandardError(), 1063 s = str(self.process.readAllStandardError(),
955 Preferences.getSystem("IOEncoding"), 1064 self.vcs.getEncoding(), 'replace')
956 'replace')
957 self.__showError(s) 1065 self.__showError(s)
958 1066
959 def __showError(self, out): 1067 def __showError(self, out):
960 """ 1068 """
961 Private slot to show some error. 1069 Private slot to show some error.
972 1080
973 @param rev1 first revision number (integer) 1081 @param rev1 first revision number (integer)
974 @param rev2 second revision number (integer) 1082 @param rev2 second revision number (integer)
975 """ 1083 """
976 if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked(): 1084 if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked():
977 self.vcs.hgSbsDiff(self.filename, revisions=(str(rev1), str(rev2))) 1085 self.vcs.hgSbsDiff(self.__filename,
1086 revisions=(str(rev1), str(rev2)))
978 else: 1087 else:
979 if self.diff is None: 1088 if self.diff is None:
980 from .HgDiffDialog import HgDiffDialog 1089 from .HgDiffDialog import HgDiffDialog
981 self.diff = HgDiffDialog(self.vcs) 1090 self.diff = HgDiffDialog(self.vcs)
982 self.diff.show() 1091 self.diff.show()
983 self.diff.raise_() 1092 self.diff.raise_()
984 self.diff.start(self.filename, [rev1, rev2], self.bundle) 1093 self.diff.start(self.__filename, [rev1, rev2], self.__bundle)
985 1094
986 def on_buttonBox_clicked(self, button): 1095 def on_buttonBox_clicked(self, button):
987 """ 1096 """
988 Private slot called by a button of the button box clicked. 1097 Private slot called by a button of the button box clicked.
989 1098
1025 self.diffP1Button.setEnabled(False) 1134 self.diffP1Button.setEnabled(False)
1026 self.diffP2Button.setEnabled(False) 1135 self.diffP2Button.setEnabled(False)
1027 1136
1028 self.diffRevisionsButton.setEnabled(False) 1137 self.diffRevisionsButton.setEnabled(False)
1029 1138
1030 def __updatePhaseButton(self): 1139 def __updateToolMenuActions(self):
1031 """ 1140 """
1032 Private slot to update the status of the phase button. 1141 Private slot to update the status of the tool menu actions and
1033 """ 1142 the tool menu button.
1034 if self.initialCommandMode == "log": 1143 """
1035 # step 1: count entries with changeable phases 1144 if self.initialCommandMode == "log" and self.projectMode:
1036 secret = 0 1145 if self.__phaseAct is not None:
1037 draft = 0 1146 # step 1: count entries with changeable phases
1038 public = 0 1147 secret = 0
1039 for itm in self.logTree.selectedItems(): 1148 draft = 0
1040 phase = itm.text(self.PhaseColumn) 1149 public = 0
1041 if phase == "draft": 1150 for itm in self.logTree.selectedItems():
1042 draft += 1 1151 phase = itm.text(self.PhaseColumn)
1043 elif phase == "secret": 1152 if phase == "draft":
1044 secret += 1 1153 draft += 1
1154 elif phase == "secret":
1155 secret += 1
1156 else:
1157 public += 1
1158
1159 # step 2: set the status of the phase button
1160 if public == 0 and \
1161 ((secret > 0 and draft == 0) or
1162 (secret == 0 and draft > 0)):
1163 self.__phaseAct.setEnabled(True)
1045 else: 1164 else:
1046 public += 1 1165 self.__phaseAct.setEnabled(False)
1047 1166
1048 # step 2: set the status of the phase button 1167 if self.__graftAct is not None:
1049 if public == 0 and \
1050 ((secret > 0 and draft == 0) or
1051 (secret == 0 and draft > 0)):
1052 self.phaseButton.setEnabled(True)
1053 else:
1054 self.phaseButton.setEnabled(False)
1055 else:
1056 self.phaseButton.setEnabled(False)
1057
1058 def __updateGraftButton(self):
1059 """
1060 Private slot to update the status of the graft button.
1061 """
1062 if self.graftButton.isVisible():
1063 if self.initialCommandMode == "log":
1064 # step 1: count selected entries not belonging to the 1168 # step 1: count selected entries not belonging to the
1065 # current branch 1169 # current branch
1066 otherBranches = 0 1170 otherBranches = 0
1067 for itm in self.logTree.selectedItems(): 1171 for itm in self.logTree.selectedItems():
1068 branch = itm.text(self.BranchColumn) 1172 branch = itm.text(self.BranchColumn)
1069 if branch != self.__projectBranch: 1173 if branch != self.__projectBranch:
1070 otherBranches += 1 1174 otherBranches += 1
1071 1175
1072 # step 2: set the status of the graft button 1176 # step 2: set the status of the graft action
1073 self.graftButton.setEnabled(otherBranches > 0) 1177 self.__graftAct.setEnabled(otherBranches > 0)
1074 else: 1178
1075 self.graftButton.setEnabled(False) 1179 self.__tagAct.setEnabled(len(self.logTree.selectedItems()) == 1)
1180 self.__switchAct.setEnabled(len(self.logTree.selectedItems()) == 1)
1181
1182 if self.__lfPullAct is not None:
1183 if self.vcs.isExtensionActive("largefiles"):
1184 self.__lfPullAct.setEnabled(bool(
1185 self.logTree.selectedItems()))
1186 else:
1187 self.__lfPullAct.setEnabled(False)
1188
1189 self.actionsButton.setEnabled(True)
1190 else:
1191 self.actionsButton.setEnabled(False)
1076 1192
1077 def __updateGui(self, itm): 1193 def __updateGui(self, itm):
1078 """ 1194 """
1079 Private slot to update GUI elements except the diff and phase buttons. 1195 Private slot to update GUI elements except tool menu actions.
1080 1196
1081 @param itm reference to the item the update should be based on 1197 @param itm reference to the item the update should be based on
1082 (QTreeWidgetItem) 1198 (QTreeWidgetItem)
1083 """ 1199 """
1084 self.messageEdit.clear() 1200 self.messageEdit.clear()
1104 @param current reference to the new current item (QTreeWidgetItem) 1220 @param current reference to the new current item (QTreeWidgetItem)
1105 @param previous reference to the old current item (QTreeWidgetItem) 1221 @param previous reference to the old current item (QTreeWidgetItem)
1106 """ 1222 """
1107 self.__updateGui(current) 1223 self.__updateGui(current)
1108 self.__updateDiffButtons() 1224 self.__updateDiffButtons()
1109 self.__updatePhaseButton() 1225 self.__updateToolMenuActions()
1110 self.__updateGraftButton()
1111 1226
1112 @pyqtSlot() 1227 @pyqtSlot()
1113 def on_logTree_itemSelectionChanged(self): 1228 def on_logTree_itemSelectionChanged(self):
1114 """ 1229 """
1115 Private slot called, when the selection has changed. 1230 Private slot called, when the selection has changed.
1116 """ 1231 """
1117 if len(self.logTree.selectedItems()) == 1: 1232 if len(self.logTree.selectedItems()) == 1:
1118 self.__updateGui(self.logTree.selectedItems()[0]) 1233 self.__updateGui(self.logTree.selectedItems()[0])
1119 1234
1120 self.__updateDiffButtons() 1235 self.__updateDiffButtons()
1121 self.__updatePhaseButton() 1236 self.__updateToolMenuActions()
1122 self.__updateGraftButton()
1123 1237
1124 @pyqtSlot() 1238 @pyqtSlot()
1125 def on_nextButton_clicked(self): 1239 def on_nextButton_clicked(self):
1126 """ 1240 """
1127 Private slot to handle the Next button. 1241 Private slot to handle the Next button.
1237 to_ = self.toDate.date().addDays(1).toString("yyyy-MM-dd") 1351 to_ = self.toDate.date().addDays(1).toString("yyyy-MM-dd")
1238 branch = self.branchCombo.currentText() 1352 branch = self.branchCombo.currentText()
1239 closedBranch = branch + '--' 1353 closedBranch = branch + '--'
1240 1354
1241 txt = self.fieldCombo.currentText() 1355 txt = self.fieldCombo.currentText()
1242 if txt == self.trUtf8("Author"): 1356 if txt == self.tr("Author"):
1243 fieldIndex = self.AuthorColumn 1357 fieldIndex = self.AuthorColumn
1244 searchRx = QRegExp(self.rxEdit.text(), Qt.CaseInsensitive) 1358 searchRx = QRegExp(self.rxEdit.text(), Qt.CaseInsensitive)
1245 elif txt == self.trUtf8("Revision"): 1359 elif txt == self.tr("Revision"):
1246 fieldIndex = self.RevisionColumn 1360 fieldIndex = self.RevisionColumn
1247 txt = self.rxEdit.text() 1361 txt = self.rxEdit.text()
1248 if txt.startswith("^"): 1362 if txt.startswith("^"):
1249 searchRx = QRegExp("^\s*{0}".format(txt[1:]), 1363 searchRx = QRegExp("^\s*{0}".format(txt[1:]),
1250 Qt.CaseInsensitive) 1364 Qt.CaseInsensitive)
1295 1409
1296 self.inputGroup.setEnabled(True) 1410 self.inputGroup.setEnabled(True)
1297 self.inputGroup.show() 1411 self.inputGroup.show()
1298 self.refreshButton.setEnabled(False) 1412 self.refreshButton.setEnabled(False)
1299 1413
1300 self.__initData() 1414 if self.initialCommandMode in ("incoming", "outgoing"):
1415 self.nextButton.setEnabled(False)
1416 self.limitSpinBox.setEnabled(False)
1417 else:
1418 self.nextButton.setEnabled(True)
1419 self.limitSpinBox.setEnabled(True)
1301 1420
1302 self.commandMode = self.initialCommandMode 1421 self.commandMode = self.initialCommandMode
1303 self.start(self.filename) 1422 self.start(self.__filename, isFile=self.__isFile)
1304 1423
1305 def on_passwordCheckBox_toggled(self, isOn): 1424 def on_passwordCheckBox_toggled(self, isOn):
1306 """ 1425 """
1307 Private slot to handle the password checkbox toggled. 1426 Private slot to handle the password checkbox toggled.
1308 1427
1352 evt.accept() 1471 evt.accept()
1353 return 1472 return
1354 super(HgLogBrowserDialog, self).keyPressEvent(evt) 1473 super(HgLogBrowserDialog, self).keyPressEvent(evt)
1355 1474
1356 @pyqtSlot() 1475 @pyqtSlot()
1357 def on_phaseButton_clicked(self): 1476 def __phaseActTriggered(self):
1358 """ 1477 """
1359 Private slot to handle the Change Phase button. 1478 Private slot to handle the Change Phase action.
1360 """ 1479 """
1361 currentPhase = self.logTree.selectedItems()[0].text(self.PhaseColumn) 1480 currentPhase = self.logTree.selectedItems()[0].text(self.PhaseColumn)
1362 revs = [] 1481 revs = []
1363 for itm in self.logTree.selectedItems(): 1482 for itm in self.logTree.selectedItems():
1364 if itm.text(self.PhaseColumn) == currentPhase: 1483 if itm.text(self.PhaseColumn) == currentPhase:
1365 revs.append( 1484 revs.append(
1366 itm.text(self.RevisionColumn).split(":")[0].strip()) 1485 itm.text(self.RevisionColumn).split(":")[0].strip())
1367 1486
1368 if not revs: 1487 if not revs:
1369 self.phaseButton.setEnabled(False) 1488 self.__phaseAct.setEnabled(False)
1370 return 1489 return
1371 1490
1372 if currentPhase == "draft": 1491 if currentPhase == "draft":
1373 newPhase = "secret" 1492 newPhase = "secret"
1374 data = (revs, "s", True) 1493 data = (revs, "s", True)
1379 if res: 1498 if res:
1380 for itm in self.logTree.selectedItems(): 1499 for itm in self.logTree.selectedItems():
1381 itm.setText(self.PhaseColumn, newPhase) 1500 itm.setText(self.PhaseColumn, newPhase)
1382 1501
1383 @pyqtSlot() 1502 @pyqtSlot()
1384 def on_graftButton_clicked(self): 1503 def __graftActTriggered(self):
1385 """ 1504 """
1386 Private slot to handle the Copy Changesets button. 1505 Private slot to handle the Copy Changesets action.
1387 """ 1506 """
1388 revs = [] 1507 revs = []
1389 1508
1390 for itm in self.logTree.selectedItems(): 1509 for itm in self.logTree.selectedItems():
1391 branch = itm.text(self.BranchColumn) 1510 branch = itm.text(self.BranchColumn)
1396 if revs: 1515 if revs:
1397 shouldReopen = self.vcs.hgGraft(self.repodir, revs) 1516 shouldReopen = self.vcs.hgGraft(self.repodir, revs)
1398 if shouldReopen: 1517 if shouldReopen:
1399 res = E5MessageBox.yesNo( 1518 res = E5MessageBox.yesNo(
1400 None, 1519 None,
1401 self.trUtf8("Copy Changesets"), 1520 self.tr("Copy Changesets"),
1402 self.trUtf8( 1521 self.tr(
1403 """The project should be reread. Do this now?"""), 1522 """The project should be reread. Do this now?"""),
1404 yesDefault=True) 1523 yesDefault=True)
1405 if res: 1524 if res:
1406 e5App().getObject("Project").reopenProject() 1525 e5App().getObject("Project").reopenProject()
1407 else: 1526 return
1527
1528 self.on_refreshButton_clicked()
1529
1530 @pyqtSlot()
1531 def __tagActTriggered(self):
1532 """
1533 Private slot to tag the selected revision.
1534 """
1535 if len(self.logTree.selectedItems()) == 1:
1536 itm = self.logTree.selectedItems()[0]
1537 rev = itm.text(self.RevisionColumn).strip().split(":", 1)[0]
1538 tag = itm.text(self.TagsColumn).strip().split(", ", 1)[0]
1539 res = self.vcs.vcsTag(self.repodir, revision=rev, tagName=tag)
1540 if res:
1408 self.on_refreshButton_clicked() 1541 self.on_refreshButton_clicked()
1542
1543 @pyqtSlot()
1544 def __switchActTriggered(self):
1545 """
1546 Private slot to switch the working directory to the
1547 selected revision.
1548 """
1549 if len(self.logTree.selectedItems()) == 1:
1550 itm = self.logTree.selectedItems()[0]
1551 rev = itm.text(self.RevisionColumn).strip().split(":", 1)[0]
1552 if rev:
1553 shouldReopen = self.vcs.vcsUpdate(self.repodir, revision=rev)
1554 if shouldReopen:
1555 res = E5MessageBox.yesNo(
1556 None,
1557 self.tr("Switch"),
1558 self.tr(
1559 """The project should be reread. Do this now?"""),
1560 yesDefault=True)
1561 if res:
1562 e5App().getObject("Project").reopenProject()
1563 return
1564
1565 self.on_refreshButton_clicked()
1566
1567 def __lfPullActTriggered(self):
1568 """
1569 Private slot to pull large files of selected revisions.
1570 """
1571 revs = []
1572 for itm in self.logTree.selectedItems():
1573 rev = itm.text(self.RevisionColumn).strip().split(":", 1)[0]
1574 if rev:
1575 revs.append(rev)
1576
1577 if revs:
1578 self.vcs.getExtensionObject("largefiles").hgLfPull(
1579 self.repodir, revisions=revs)

eric ide

mercurial