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 |
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: |
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)"): |
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 """ |
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) |
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) |
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) |