--- a/src/eric7/Plugins/VcsPlugins/vcsMercurial/HgLogBrowserDialog.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/Plugins/VcsPlugins/vcsMercurial/HgLogBrowserDialog.py Wed Jul 13 14:55:47 2022 +0200 @@ -15,11 +15,24 @@ from PyQt6.QtCore import pyqtSlot, Qt, QDate, QSize, QPoint from PyQt6.QtGui import ( - QColor, QPixmap, QPainter, QPen, QBrush, QIcon, QTextCursor, QPalette + QColor, + QPixmap, + QPainter, + QPen, + QBrush, + QIcon, + QTextCursor, + QPalette, ) from PyQt6.QtWidgets import ( - QWidget, QDialogButtonBox, QHeaderView, QTreeWidgetItem, QApplication, - QLineEdit, QMenu, QInputDialog + QWidget, + QDialogButtonBox, + QHeaderView, + QTreeWidgetItem, + QApplication, + QLineEdit, + QMenu, + QInputDialog, ) from EricWidgets.EricApplication import ericApp @@ -35,21 +48,47 @@ import Preferences import Utilities -COLORNAMES = ["blue", "darkgreen", "red", "green", "darkblue", "purple", - "cyan", "olive", "magenta", "darkred", "darkmagenta", - "darkcyan", "gray", "yellow"] +COLORNAMES = [ + "blue", + "darkgreen", + "red", + "green", + "darkblue", + "purple", + "cyan", + "olive", + "magenta", + "darkred", + "darkmagenta", + "darkcyan", + "gray", + "yellow", +] COLORS = [str(QColor(x).name()) for x in COLORNAMES] -LIGHTCOLORS = ["#aaaaff", "#7faa7f", "#ffaaaa", "#aaffaa", "#7f7faa", - "#ffaaff", "#aaffff", "#d5d579", "#ffaaff", "#d57979", - "#d579d5", "#79d5d5", "#d5d5d5", "#d5d500", - ] +LIGHTCOLORS = [ + "#aaaaff", + "#7faa7f", + "#ffaaaa", + "#aaffaa", + "#7f7faa", + "#ffaaff", + "#aaffff", + "#d5d579", + "#ffaaff", + "#d57979", + "#d579d5", + "#79d5d5", + "#d5d5d5", + "#d5d500", +] class HgLogBrowserDialog(QWidget, Ui_HgLogBrowserDialog): """ Class implementing a dialog to browse the log history. """ + IconColumn = 0 BranchColumn = 1 RevisionColumn = 2 @@ -59,20 +98,20 @@ MessageColumn = 6 TagsColumn = 7 BookmarksColumn = 8 - + LargefilesCacheL = ".hglf/" LargefilesCacheW = ".hglf\\" PathSeparatorRe = re.compile(r"/|\\") - + GraftedRe = re.compile(r"\(grafted from ([0-9a-fA-F]+)\)") GraftedTemplate = '(grafted from <a href="chg:{0}">{0}</a>)' - + ClosedIndicator = " \u2612" - + def __init__(self, vcs, mode="", parent=None): """ Constructor - + @param vcs reference to the vcs object @type Hg @param mode mode of the dialog @@ -82,23 +121,23 @@ """ super().__init__(parent) self.setupUi(self) - + windowFlags = self.windowFlags() windowFlags |= Qt.WindowType.WindowContextHelpButtonHint self.setWindowFlags(windowFlags) - + self.mainSplitter.setSizes([300, 400]) self.mainSplitter.setStretchFactor(0, 1) self.mainSplitter.setStretchFactor(1, 2) self.diffSplitter.setStretchFactor(0, 1) self.diffSplitter.setStretchFactor(1, 2) - + if not mode: if vcs.getPlugin().getPreferences("LogBrowserShowFullLog"): mode = "full_log" else: mode = "log" - + if mode == "log": self.setWindowTitle(self.tr("Mercurial Log")) elif mode == "incoming": @@ -107,45 +146,44 @@ self.setWindowTitle(self.tr("Mercurial Log (Outgoing)")) elif mode == "full_log": self.setWindowTitle(self.tr("Mercurial Full Log")) - - self.buttonBox.button( - QDialogButtonBox.StandardButton.Close).setEnabled(False) - self.buttonBox.button( - QDialogButtonBox.StandardButton.Cancel).setDefault(True) - + + self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True) + self.filesTree.headerItem().setText(self.filesTree.columnCount(), "") - self.filesTree.header().setSortIndicator( - 0, Qt.SortOrder.AscendingOrder) - + self.filesTree.header().setSortIndicator(0, Qt.SortOrder.AscendingOrder) + self.upButton.setIcon(UI.PixmapCache.getIcon("1uparrow")) self.downButton.setIcon(UI.PixmapCache.getIcon("1downarrow")) - + self.refreshButton = self.buttonBox.addButton( - self.tr("&Refresh"), QDialogButtonBox.ButtonRole.ActionRole) + self.tr("&Refresh"), QDialogButtonBox.ButtonRole.ActionRole + ) self.refreshButton.setToolTip( - self.tr("Press to refresh the list of changesets")) + self.tr("Press to refresh the list of changesets") + ) self.refreshButton.setEnabled(False) - + self.findPrevButton.setIcon(UI.PixmapCache.getIcon("1leftarrow")) self.findNextButton.setIcon(UI.PixmapCache.getIcon("1rightarrow")) self.__findBackwards = False - + self.modeComboBox.addItem(self.tr("Find"), "find") self.modeComboBox.addItem(self.tr("Filter"), "filter") - + self.fieldCombo.addItem(self.tr("Revision"), "revision") self.fieldCombo.addItem(self.tr("Author"), "author") self.fieldCombo.addItem(self.tr("Message"), "message") self.fieldCombo.addItem(self.tr("File"), "file") self.fieldCombo.addItem(self.tr("Phase"), "phase") - + font = Preferences.getEditorOtherFonts("MonospacedFont") self.diffEdit.document().setDefaultFont(font) - + self.diffHighlighter = HgDiffHighlighter(self.diffEdit.document()) self.__diffGenerator = HgDiffGenerator(vcs, self) self.__diffGenerator.finished.connect(self.__generatorFinished) - + self.vcs = vcs if mode in ("log", "incoming", "outgoing", "full_log"): if mode == "full_log": @@ -157,7 +195,7 @@ self.commandMode = "log" self.initialCommandMode = "log" self.__hgClient = vcs.getClient() - + self.__detailsTemplate = self.tr( "<table>" "<tr><td><b>Revision</b></td><td>{0}</td></tr>" @@ -168,36 +206,32 @@ "<tr><td><b>Message</b></td><td>{5}</td></tr>" "</table>" ) - self.__parentsTemplate = self.tr( - "<tr><td><b>Parents</b></td><td>{0}</td></tr>" - ) + self.__parentsTemplate = self.tr("<tr><td><b>Parents</b></td><td>{0}</td></tr>") self.__childrenTemplate = self.tr( "<tr><td><b>Children</b></td><td>{0}</td></tr>" ) - self.__tagsTemplate = self.tr( - "<tr><td><b>Tags</b></td><td>{0}</td></tr>" - ) + self.__tagsTemplate = self.tr("<tr><td><b>Tags</b></td><td>{0}</td></tr>") self.__latestTagTemplate = self.tr( "<tr><td><b>Latest Tag</b></td><td>{0}</td></tr>" ) self.__bookmarksTemplate = self.tr( "<tr><td><b>Bookmarks</b></td><td>{0}</td></tr>" ) - + self.__bundle = "" self.__filename = "" self.__isFile = False self.__selectedRevisions = [] self.intercept = False - + self.__initData() - + self.__allBranchesFilter = self.tr("All") - + self.fromDate.setDisplayFormat("yyyy-MM-dd") self.toDate.setDisplayFormat("yyyy-MM-dd") self.__resetUI() - + # roles used in the log tree self.__messageRole = Qt.ItemDataRole.UserRole self.__changesRole = Qt.ItemDataRole.UserRole + 1 @@ -205,58 +239,56 @@ self.__parentsRole = Qt.ItemDataRole.UserRole + 3 self.__latestTagRole = Qt.ItemDataRole.UserRole + 4 self.__incomingRole = Qt.ItemDataRole.UserRole + 5 - + # roles used in the file tree self.__diffFileLineRole = Qt.ItemDataRole.UserRole - + self.flags = { - 'A': self.tr('Added'), - 'D': self.tr('Deleted'), - 'M': self.tr('Modified'), + "A": self.tr("Added"), + "D": self.tr("Deleted"), + "M": self.tr("Modified"), } - + self.phases = { - 'draft': self.tr("Draft"), - 'public': self.tr("Public"), - 'secret': self.tr("Secret"), + "draft": self.tr("Draft"), + "public": self.tr("Public"), + "secret": self.tr("Secret"), } - + self.__dotRadius = 8 self.__rowHeight = 20 - - self.logTree.setIconSize( - QSize(100 * self.__rowHeight, self.__rowHeight)) + + self.logTree.setIconSize(QSize(100 * self.__rowHeight, self.__rowHeight)) self.BookmarksColumn = self.logTree.columnCount() - self.logTree.headerItem().setText( - self.BookmarksColumn, self.tr("Bookmarks")) - + self.logTree.headerItem().setText(self.BookmarksColumn, self.tr("Bookmarks")) + self.__logTreeNormalFont = self.logTree.font() self.__logTreeNormalFont.setBold(False) self.__logTreeBoldFont = self.logTree.font() self.__logTreeBoldFont.setBold(True) self.__logTreeHasDarkBackground = ericApp().usesDarkPalette() - + self.detailsEdit.anchorClicked.connect(self.__revisionClicked) - + self.__initActionsMenu() - + self.__finishCallbacks = [] if self.initialCommandMode == "full_log": self.__addFinishCallback(self.on_nextButton_clicked) - + def __addFinishCallback(self, callback): """ Private method to add a method to be called once the process finished. - + The callback methods are invoke in a FIFO style and are consumed. If a callback method needs to be called again, it must be added again. - + @param callback callback method @type function """ if callback not in self.__finishCallbacks: self.__finishCallbacks.append(callback) - + def __initActionsMenu(self): """ Private method to initialize the actions menu. @@ -264,138 +296,181 @@ self.__actionsMenu = QMenu() self.__actionsMenu.setTearOffEnabled(True) self.__actionsMenu.setToolTipsVisible(True) - + self.__graftAct = self.__actionsMenu.addAction( UI.PixmapCache.getIcon("vcsGraft"), - self.tr("Copy Changesets"), self.__graftActTriggered) - self.__graftAct.setToolTip(self.tr( - "Copy the selected changesets to the current branch")) - + self.tr("Copy Changesets"), + self.__graftActTriggered, + ) + self.__graftAct.setToolTip( + self.tr("Copy the selected changesets to the current branch") + ) + self.__mergeAct = self.__actionsMenu.addAction( UI.PixmapCache.getIcon("vcsMerge"), - self.tr("Merge with Changeset"), self.__mergeActTriggered) - self.__mergeAct.setToolTip(self.tr( - "Merge the working directory with the selected changeset")) - + self.tr("Merge with Changeset"), + self.__mergeActTriggered, + ) + self.__mergeAct.setToolTip( + self.tr("Merge the working directory with the selected changeset") + ) + self.__phaseAct = self.__actionsMenu.addAction( - self.tr("Change Phase"), self.__phaseActTriggered) - self.__phaseAct.setToolTip(self.tr( - "Change the phase of the selected revisions")) - self.__phaseAct.setWhatsThis(self.tr( - """<b>Change Phase</b>\n<p>This changes the phase of the""" - """ selected revisions. The selected revisions have to have""" - """ the same current phase.</p>""")) - + self.tr("Change Phase"), self.__phaseActTriggered + ) + self.__phaseAct.setToolTip( + self.tr("Change the phase of the selected revisions") + ) + self.__phaseAct.setWhatsThis( + self.tr( + """<b>Change Phase</b>\n<p>This changes the phase of the""" + """ selected revisions. The selected revisions have to have""" + """ the same current phase.</p>""" + ) + ) + self.__tagAct = self.__actionsMenu.addAction( - UI.PixmapCache.getIcon("vcsTag"), self.tr("Tag"), - self.__tagActTriggered) + UI.PixmapCache.getIcon("vcsTag"), self.tr("Tag"), self.__tagActTriggered + ) self.__tagAct.setToolTip(self.tr("Tag the selected revision")) - + self.__closeHeadsAct = self.__actionsMenu.addAction( - UI.PixmapCache.getIcon("closehead"), self.tr("Close Heads"), - self.__closeHeadsActTriggered) + UI.PixmapCache.getIcon("closehead"), + self.tr("Close Heads"), + self.__closeHeadsActTriggered, + ) self.__closeHeadsAct.setToolTip(self.tr("Close the selected heads")) - + self.__switchAct = self.__actionsMenu.addAction( - UI.PixmapCache.getIcon("vcsSwitch"), self.tr("Switch"), - self.__switchActTriggered) - self.__switchAct.setToolTip(self.tr( - "Switch the working directory to the selected revision")) - + UI.PixmapCache.getIcon("vcsSwitch"), + self.tr("Switch"), + self.__switchActTriggered, + ) + self.__switchAct.setToolTip( + self.tr("Switch the working directory to the selected revision") + ) + self.__actionsMenu.addSeparator() - + self.__bookmarkAct = self.__actionsMenu.addAction( UI.PixmapCache.getIcon("addBookmark"), - self.tr("Define Bookmark..."), self.__bookmarkActTriggered) - self.__bookmarkAct.setToolTip( - self.tr("Bookmark the selected revision")) + self.tr("Define Bookmark..."), + self.__bookmarkActTriggered, + ) + self.__bookmarkAct.setToolTip(self.tr("Bookmark the selected revision")) self.__bookmarkMoveAct = self.__actionsMenu.addAction( UI.PixmapCache.getIcon("moveBookmark"), - self.tr("Move Bookmark..."), self.__bookmarkMoveActTriggered) + self.tr("Move Bookmark..."), + self.__bookmarkMoveActTriggered, + ) self.__bookmarkMoveAct.setToolTip( - self.tr("Move bookmark to the selected revision")) - + self.tr("Move bookmark to the selected revision") + ) + self.__actionsMenu.addSeparator() - + self.__pullAct = self.__actionsMenu.addAction( - UI.PixmapCache.getIcon("vcsUpdate"), self.tr("Pull Changes"), - self.__pullActTriggered) - self.__pullAct.setToolTip(self.tr( - "Pull changes from a remote repository")) + UI.PixmapCache.getIcon("vcsUpdate"), + self.tr("Pull Changes"), + self.__pullActTriggered, + ) + self.__pullAct.setToolTip(self.tr("Pull changes from a remote repository")) self.__lfPullAct = self.__actionsMenu.addAction( - self.tr("Pull Large Files"), self.__lfPullActTriggered) - self.__lfPullAct.setToolTip(self.tr( - "Pull large files for selected revisions")) - + self.tr("Pull Large Files"), self.__lfPullActTriggered + ) + self.__lfPullAct.setToolTip(self.tr("Pull large files for selected revisions")) + self.__actionsMenu.addSeparator() - + self.__pushAct = self.__actionsMenu.addAction( UI.PixmapCache.getIcon("vcsCommit"), - self.tr("Push Selected Changes"), self.__pushActTriggered) - self.__pushAct.setToolTip(self.tr( - "Push changes of the selected changeset and its ancestors" - " to a remote repository")) + self.tr("Push Selected Changes"), + self.__pushActTriggered, + ) + self.__pushAct.setToolTip( + self.tr( + "Push changes of the selected changeset and its ancestors" + " to a remote repository" + ) + ) self.__pushAllAct = self.__actionsMenu.addAction( UI.PixmapCache.getIcon("vcsCommit"), - self.tr("Push All Changes"), self.__pushAllActTriggered) - self.__pushAllAct.setToolTip(self.tr( - "Push all changes to a remote repository")) - + self.tr("Push All Changes"), + self.__pushAllActTriggered, + ) + self.__pushAllAct.setToolTip(self.tr("Push all changes to a remote repository")) + self.__actionsMenu.addSeparator() - + self.__bundleAct = self.__actionsMenu.addAction( UI.PixmapCache.getIcon("vcsCreateChangegroup"), - self.tr("Create Changegroup"), self.__bundleActTriggered) - self.__bundleAct.setToolTip(self.tr( - "Create a changegroup file containing the selected changesets")) - self.__bundleAct.setWhatsThis(self.tr( - """<b>Create Changegroup</b>\n<p>This creates a changegroup""" - """ file containing the selected revisions. If no revisions""" - """ are selected, all changesets will be bundled. If one""" - """ revision is selected, it will be interpreted as the base""" - """ revision. Otherwise the lowest revision will be used as""" - """ the base revision and all other revision will be bundled.""" - """ If the dialog is showing outgoing changesets, all""" - """ selected changesets will be bundled.</p>""")) + self.tr("Create Changegroup"), + self.__bundleActTriggered, + ) + self.__bundleAct.setToolTip( + self.tr("Create a changegroup file containing the selected changesets") + ) + self.__bundleAct.setWhatsThis( + self.tr( + """<b>Create Changegroup</b>\n<p>This creates a changegroup""" + """ file containing the selected revisions. If no revisions""" + """ are selected, all changesets will be bundled. If one""" + """ revision is selected, it will be interpreted as the base""" + """ revision. Otherwise the lowest revision will be used as""" + """ the base revision and all other revision will be bundled.""" + """ If the dialog is showing outgoing changesets, all""" + """ selected changesets will be bundled.</p>""" + ) + ) self.__unbundleAct = self.__actionsMenu.addAction( UI.PixmapCache.getIcon("vcsApplyChangegroup"), - self.tr("Apply Changegroup"), self.__unbundleActTriggered) - self.__unbundleAct.setToolTip(self.tr( - "Apply the currently viewed changegroup file")) - + self.tr("Apply Changegroup"), + self.__unbundleActTriggered, + ) + self.__unbundleAct.setToolTip( + self.tr("Apply the currently viewed changegroup file") + ) + self.__actionsMenu.addSeparator() - + self.__gpgSignAct = self.__actionsMenu.addAction( UI.PixmapCache.getIcon("changesetSign"), - self.tr("Sign Revisions"), self.__gpgSignActTriggered) - self.__gpgSignAct.setToolTip(self.tr( - "Add a signature for the selected revisions")) + self.tr("Sign Revisions"), + self.__gpgSignActTriggered, + ) + self.__gpgSignAct.setToolTip( + self.tr("Add a signature for the selected revisions") + ) self.__gpgVerifyAct = self.__actionsMenu.addAction( UI.PixmapCache.getIcon("changesetSignVerify"), - self.tr("Verify Signatures"), self.__gpgVerifyActTriggered) - self.__gpgVerifyAct.setToolTip(self.tr( - "Verify all signatures there may be for the selected revision")) - + self.tr("Verify Signatures"), + self.__gpgVerifyActTriggered, + ) + self.__gpgVerifyAct.setToolTip( + self.tr("Verify all signatures there may be for the selected revision") + ) + self.__actionsMenu.addSeparator() - + self.__stripAct = self.__actionsMenu.addAction( UI.PixmapCache.getIcon("fileDelete"), - self.tr("Strip Changesets"), self.__stripActTriggered) - self.__stripAct.setToolTip(self.tr( - "Strip changesets from a repository")) - + self.tr("Strip Changesets"), + self.__stripActTriggered, + ) + self.__stripAct.setToolTip(self.tr("Strip changesets from a repository")) + self.__actionsMenu.addSeparator() - + self.__selectAllAct = self.__actionsMenu.addAction( - self.tr("Select All Entries"), self.__selectAllActTriggered) + self.tr("Select All Entries"), self.__selectAllActTriggered + ) self.__unselectAllAct = self.__actionsMenu.addAction( - self.tr("Deselect All Entries"), - lambda: self.__selectAllActTriggered(False)) - - self.actionsButton.setIcon( - UI.PixmapCache.getIcon("actionsToolButton")) + self.tr("Deselect All Entries"), lambda: self.__selectAllActTriggered(False) + ) + + self.actionsButton.setIcon(UI.PixmapCache.getIcon("actionsToolButton")) self.actionsButton.setMenu(self.__actionsMenu) - + def __initData(self): """ Private method to (re-)initialize some data. @@ -403,46 +478,46 @@ self.__maxDate = QDate() self.__minDate = QDate() self.__filterLogsEnabled = True - - self.buf = [] # buffer for stdout + + self.buf = [] # buffer for stdout self.diff = None self.__started = False self.__lastRev = 0 self.projectMode = False - + # attributes to store log graph data self.__revs = [] self.__revColors = {} self.__revColor = 0 - + self.__branchColors = {} - + self.__projectWorkingDirParents = [] self.__projectBranch = "" - + self.__childrenInfo = collections.defaultdict(list) - + def closeEvent(self, e): """ Protected slot implementing a close event handler. - + @param e close event (QCloseEvent) """ if self.__hgClient.isExecuting(): self.__hgClient.cancel() - + + self.vcs.getPlugin().setPreferences("LogBrowserGeometry", self.saveGeometry()) self.vcs.getPlugin().setPreferences( - "LogBrowserGeometry", self.saveGeometry()) - self.vcs.getPlugin().setPreferences( - "LogBrowserSplitterStates", [ + "LogBrowserSplitterStates", + [ self.mainSplitter.saveState(), self.detailsSplitter.saveState(), self.diffSplitter.saveState(), - ] + ], ) - + e.accept() - + def show(self): """ Public slot to show the dialog. @@ -450,7 +525,7 @@ self.__reloadGeometry() self.__restoreSplitterStates() self.__resetUI() - + super().show() def __reloadGeometry(self): @@ -463,19 +538,18 @@ self.resize(s) else: self.restoreGeometry(geom) - + def __restoreSplitterStates(self): """ Private method to restore the state of the various splitters. """ - states = self.vcs.getPlugin().getPreferences( - "LogBrowserSplitterStates") + states = self.vcs.getPlugin().getPreferences("LogBrowserSplitterStates") if len(states) == 3: # we have three splitters self.mainSplitter.restoreState(states[0]) self.detailsSplitter.restoreState(states[1]) self.diffSplitter.restoreState(states[2]) - + def __resetUI(self): """ Private method to reset the user interface. @@ -484,55 +558,53 @@ self.fromDate.setDate(QDate.currentDate()) self.toDate.setDate(QDate.currentDate()) self.fieldCombo.setCurrentIndex(self.fieldCombo.findData("message")) - self.limitSpinBox.setValue(self.vcs.getPlugin().getPreferences( - "LogLimit")) - self.stopCheckBox.setChecked(self.vcs.getPlugin().getPreferences( - "StopLogOnCopy")) - + self.limitSpinBox.setValue(self.vcs.getPlugin().getPreferences("LogLimit")) + self.stopCheckBox.setChecked( + self.vcs.getPlugin().getPreferences("StopLogOnCopy") + ) + if self.initialCommandMode in ("incoming", "outgoing"): self.nextButton.setEnabled(False) self.limitSpinBox.setEnabled(False) else: self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True) - + self.logTree.clear() - + if self.initialCommandMode == "full_log": self.commandMode = "incoming" else: self.commandMode = self.initialCommandMode - + def __resizeColumnsLog(self): """ Private method to resize the log tree columns. """ - self.logTree.header().resizeSections( - QHeaderView.ResizeMode.ResizeToContents) + self.logTree.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents) self.logTree.header().setStretchLastSection(True) - + def __resizeColumnsFiles(self): """ Private method to resize the changed files tree columns. """ - self.filesTree.header().resizeSections( - QHeaderView.ResizeMode.ResizeToContents) + self.filesTree.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents) self.filesTree.header().setStretchLastSection(True) - + def __resortFiles(self): """ Private method to resort the changed files tree. """ sortColumn = self.filesTree.sortColumn() - self.filesTree.sortItems( - 1, self.filesTree.header().sortIndicatorOrder()) + self.filesTree.sortItems(1, self.filesTree.header().sortIndicatorOrder()) self.filesTree.sortItems( - sortColumn, self.filesTree.header().sortIndicatorOrder()) - + sortColumn, self.filesTree.header().sortIndicatorOrder() + ) + def __getColor(self, n): """ Private method to get the (rotating) name of the color given an index. - + @param n color index @type int @return color name @@ -542,23 +614,22 @@ return LIGHTCOLORS[n % len(LIGHTCOLORS)] else: return COLORS[n % len(COLORS)] - + def __branchColor(self, branchName): """ Private method to calculate a color for a given branch name. - + @param branchName name of the branch (string) @return name of the color to use (string) """ if branchName not in self.__branchColors: - self.__branchColors[branchName] = self.__getColor( - len(self.__branchColors)) + self.__branchColors[branchName] = self.__getColor(len(self.__branchColors)) return self.__branchColors[branchName] - + def __generateEdges(self, rev, parents): """ Private method to generate edge info for the give data. - + @param rev revision to calculate edge info for (integer) @param parents list of parent revisions (list of integers) @return tuple containing the column and color index for @@ -571,15 +642,15 @@ self.__revs.append(rev) self.__revColors[rev] = self.__revColor self.__revColor += 1 - + col = self.__revs.index(rev) color = self.__revColors.pop(rev) nextRevs = self.__revs[:] - + # add parents to next addparents = [p for p in parents if p not in nextRevs] - nextRevs[col:col + 1] = addparents - + nextRevs[col : col + 1] = addparents + # set colors for the parents for i, p in enumerate(addparents): if not i: @@ -587,28 +658,35 @@ else: self.__revColors[p] = self.__revColor self.__revColor += 1 - + # add edges to the graph edges = [] if parents[0] != -1: for ecol, erev in enumerate(self.__revs): if erev in nextRevs: - edges.append( - (ecol, nextRevs.index(erev), self.__revColors[erev])) + edges.append((ecol, nextRevs.index(erev), self.__revColors[erev])) elif erev == rev: for p in parents: - edges.append( - (ecol, nextRevs.index(p), self.__revColors[p])) - + edges.append((ecol, nextRevs.index(p), self.__revColors[p])) + self.__revs = nextRevs return col, color, edges - - def __generateIcon(self, column, color, bottomedges, topedges, dotColor, - currentRev, closed, isPushableDraft): + + def __generateIcon( + self, + column, + color, + bottomedges, + topedges, + dotColor, + currentRev, + closed, + isPushableDraft, + ): """ Private method to generate an icon containing the revision tree for the given data. - + @param column column index of the revision @type int @param color color of the node @@ -631,32 +709,32 @@ @return icon for the node @rtype QIcon """ + def col2x(col, radius): """ Local function to calculate a x-position for a column. - + @param col column number (integer) @param radius radius of the indicator circle (integer) """ return int(1.2 * radius) * col + radius // 2 + 3 - + textColor = self.logTree.palette().color(QPalette.ColorRole.Text) - + radius = self.__dotRadius w = len(bottomedges) * radius + 20 h = self.__rowHeight - + dot_x = col2x(column, radius) - radius // 2 dot_y = h // 2 - + pix = QPixmap(w, h) - pix.fill(QColor(0, 0, 0, 0)) # draw transparent background + pix.fill(QColor(0, 0, 0, 0)) # draw transparent background painter = QPainter(pix) painter.setRenderHint(QPainter.RenderHint.Antialiasing) - + # draw the revision history lines - for y1, y2, lines in ((0, h, bottomedges), - (-h, 0, topedges)): + for y1, y2, lines in ((0, h, bottomedges), (-h, 0, topedges)): if lines: for start, end, ecolor in lines: lpen = QPen(QColor(self.__getColor(ecolor))) @@ -665,12 +743,12 @@ x1 = col2x(start, radius) x2 = col2x(end, radius) painter.drawLine(x1, dot_y + y1, x2, dot_y + y2) - + penradius = 1 pencolor = textColor - + dot_y = (h // 2) - radius // 2 - + # draw an indicator for the revision if currentRev: # enlarge for the current revision @@ -684,8 +762,7 @@ pen.setWidth(penradius) painter.setPen(pen) if closed: - painter.drawRect(dot_x - 2, dot_y + 1, - radius + 4, radius - 2) + painter.drawRect(dot_x - 2, dot_y + 1, radius + 4, radius - 2) elif self.commandMode in ("incoming", "outgoing"): offset = radius // 2 if self.commandMode == "incoming": @@ -693,14 +770,14 @@ painter.drawConvexPolygon( QPoint(dot_x, dot_y), QPoint(dot_x + 2 * offset, dot_y), - QPoint(dot_x + offset, dot_y + 2 * offset) + QPoint(dot_x + offset, dot_y + 2 * offset), ) else: # outgoing: draw an up arrow painter.drawConvexPolygon( QPoint(dot_x + offset, dot_y), QPoint(dot_x, dot_y + 2 * offset), - QPoint(dot_x + 2 * offset, dot_y + 2 * offset) + QPoint(dot_x + 2 * offset, dot_y + 2 * offset), ) else: if isPushableDraft: @@ -710,34 +787,31 @@ painter.drawConvexPolygon( QPoint(dot_x + offset, dot_y), QPoint(dot_x, dot_y + 2 * offset), - QPoint(dot_x + 2 * offset, dot_y + 2 * offset) + QPoint(dot_x + 2 * offset, dot_y + 2 * offset), ) else: painter.drawEllipse(dot_x, dot_y, radius, radius) painter.end() return QIcon(pix) - + def __getParents(self, rev): """ Private method to get the parents of the currently viewed file/directory. - + @param rev revision number to get parents for (string) @return list of parent revisions (list of integers) """ errMsg = "" parents = [-1] - + if int(rev) > 0: args = self.vcs.initCommand("parents") if self.commandMode == "incoming": if self.__bundle: args.append("--repository") args.append(self.__bundle) - elif ( - self.vcs.bundleFile and - os.path.exists(self.vcs.bundleFile) - ): + elif self.vcs.bundleFile and os.path.exists(self.vcs.bundleFile): args.append("--repository") args.append(self.vcs.bundleFile) args.append("--template") @@ -746,114 +820,101 @@ args.append(rev) if not self.projectMode: args.append(self.__filename) - + output, errMsg = self.__hgClient.runcommand(args) - + if output: parents = [int(p) for p in output.strip().splitlines()] - + return parents - + def __identifyProject(self): """ Private method to determine the revision of the project directory. """ errMsg = "" - + args = self.vcs.initCommand("identify") args.append("-nb") - + output, errMsg = self.__hgClient.runcommand(args) - + if errMsg: - EricMessageBox.critical( - self, - self.tr("Mercurial Error"), - errMsg) - + EricMessageBox.critical(self, self.tr("Mercurial Error"), errMsg) + if output: outputList = output.strip().split(None, 1) if len(outputList) == 2: outputRevs = outputList[0].strip() if outputRevs.endswith("+"): outputRevs = outputRevs[:-1] - self.__projectWorkingDirParents = outputRevs.split('+') + self.__projectWorkingDirParents = outputRevs.split("+") else: self.__projectWorkingDirParents = [outputRevs] self.__projectBranch = outputList[1].strip() - + def __getClosedBranches(self): """ Private method to get the list of closed branches. """ self.__closedBranchesRevs = [] errMsg = "" - + args = self.vcs.initCommand("branches") args.append("--closed") - + output, errMsg = self.__hgClient.runcommand(args) - + if errMsg: - EricMessageBox.critical( - self, - self.tr("Mercurial Error"), - errMsg) - + EricMessageBox.critical(self, self.tr("Mercurial Error"), errMsg) + if output: for line in output.splitlines(): if line.strip().endswith("(closed)"): parts = line.split() - self.__closedBranchesRevs.append( - parts[-2].split(":", 1)[0]) - + self.__closedBranchesRevs.append(parts[-2].split(":", 1)[0]) + def __getHeads(self): """ Private method to get the list of all heads. """ self.__headRevisions = [] errMsg = "" - + args = self.vcs.initCommand("heads") args.append("--closed") args.append("--template") args.append("{rev}\n") - + output, errMsg = self.__hgClient.runcommand(args) - + if errMsg: - EricMessageBox.critical( - self, - self.tr("Mercurial Error"), - errMsg) - + EricMessageBox.critical(self, self.tr("Mercurial Error"), errMsg) + if output: for line in output.splitlines(): line = line.strip() if line: self.__headRevisions.append(line) - + def __getRevisionOfTag(self, tag): """ Private method to get the revision of a tag. - + @param tag tag name @type str @return tuple containing the revision and changeset ID @rtype tuple of (str, str) """ errMsg = "" - + args = self.vcs.initCommand("tags") - + output, errMsg = self.__hgClient.runcommand(args) - + if errMsg: - EricMessageBox.critical( - self, - self.tr("Mercurial Error"), - errMsg) - + EricMessageBox.critical(self, self.tr("Mercurial Error"), errMsg) + res = ("", "") if output: for line in output.splitlines(): @@ -863,15 +924,27 @@ if name == tag: res = tuple(rev.split(":", 1)) break - + return res - - def __generateLogItem(self, author, date, message, revision, changedPaths, - parents, branches, tags, phase, bookmarks, - latestTag, canPush=False): + + def __generateLogItem( + self, + author, + date, + message, + revision, + changedPaths, + parents, + branches, + tags, + phase, + bookmarks, + latestTag, + canPush=False, + ): """ Private method to generate a log tree entry. - + @param author author info @type str @param date date info @@ -901,7 +974,8 @@ @rtype QTreeWidgetItem """ logMessageColumnWidth = self.vcs.getPlugin().getPreferences( - "LogMessageColumnWidth") + "LogMessageColumnWidth" + ) msgtxt = "" for line in message: if ". " in line: @@ -911,10 +985,9 @@ msgtxt += " " + line.strip() if len(msgtxt) > logMessageColumnWidth: msgtxt = "{0}...".format(msgtxt[:logMessageColumnWidth]) - + rev, node = revision.split(":") - closedStr = (self.ClosedIndicator - if rev in self.__closedBranchesRevs else "") + closedStr = self.ClosedIndicator if rev in self.__closedBranchesRevs else "" phaseStr = self.phases.get(phase, phase) columnLabels = [ "", @@ -929,16 +1002,17 @@ if bookmarks is not None: columnLabels.append(", ".join(bookmarks)) itm = QTreeWidgetItem(self.logTree, columnLabels) - - itm.setForeground(self.BranchColumn, - QBrush(QColor(self.__branchColor(branches[0])))) - + + itm.setForeground( + self.BranchColumn, QBrush(QColor(self.__branchColor(branches[0]))) + ) + if not self.projectMode: parents = self.__getParents(rev) if not parents: parents = [int(rev) - 1] column, color, edges = self.__generateEdges(int(rev), parents) - + itm.setData(0, self.__messageRole, message) itm.setData(0, self.__changesRole, changedPaths) itm.setData(0, self.__edgesRole, edges) @@ -950,112 +1024,115 @@ for parent in parents: self.__childrenInfo[parent].append(int(rev)) itm.setData(0, self.__incomingRole, self.commandMode == "incoming") - + topedges = ( - self.logTree.topLevelItem( - self.logTree.indexOfTopLevelItem(itm) - 1 - ).data(0, self.__edgesRole) - if self.logTree.topLevelItemCount() > 1 else - None + self.logTree.topLevelItem(self.logTree.indexOfTopLevelItem(itm) - 1).data( + 0, self.__edgesRole + ) + if self.logTree.topLevelItemCount() > 1 + else None ) - - icon = self.__generateIcon(column, color, edges, topedges, - QColor(self.__branchColor(branches[0])), - rev in self.__projectWorkingDirParents, - rev in self.__closedBranchesRevs, - phase == "draft" and canPush) + + icon = self.__generateIcon( + column, + color, + edges, + topedges, + QColor(self.__branchColor(branches[0])), + rev in self.__projectWorkingDirParents, + rev in self.__closedBranchesRevs, + phase == "draft" and canPush, + ) itm.setIcon(0, icon) - + try: self.__lastRev = int(revision.split(":")[0]) except ValueError: self.__lastRev = 0 - + return itm - + def __getLogEntries(self, startRev=None, noEntries=0): """ Private method to retrieve log entries from the repository. - + @param startRev revision number to start from (integer, string) @param noEntries number of entries to get (0 = default) (int) """ - self.buttonBox.button( - QDialogButtonBox.StandardButton.Close).setEnabled(False) - self.buttonBox.button( - QDialogButtonBox.StandardButton.Cancel).setEnabled(True) - self.buttonBox.button( - QDialogButtonBox.StandardButton.Cancel).setDefault(True) + self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True) QApplication.processEvents() - + with EricOverrideCursor(): self.buf = [] self.cancelled = False self.errors.clear() self.intercept = False - + if noEntries == 0: noEntries = self.limitSpinBox.value() - + preargs = [] args = self.vcs.initCommand(self.commandMode) - args.append('--verbose') + args.append("--verbose") if self.commandMode not in ("incoming", "outgoing"): - args.append('--limit') + args.append("--limit") args.append(str(noEntries)) if self.commandMode in ("incoming", "outgoing"): args.append("--newest-first") if self.vcs.hasSubrepositories(): args.append("--subrepos") if startRev is not None: - args.append('--rev') - args.append('{0}:0'.format(startRev)) - if ( - not self.projectMode and - not self.stopCheckBox.isChecked() - ): - args.append('--follow') + args.append("--rev") + args.append("{0}:0".format(startRev)) + if not self.projectMode and not self.stopCheckBox.isChecked(): + args.append("--follow") if self.commandMode == "log": - args.append('--copies') - args.append('--template') - args.append(os.path.join(os.path.dirname(__file__), - "templates", - "logBrowserBookmarkPhase.tmpl")) + args.append("--copies") + args.append("--template") + args.append( + os.path.join( + os.path.dirname(__file__), + "templates", + "logBrowserBookmarkPhase.tmpl", + ) + ) if self.commandMode == "incoming": if self.__bundle: args.append(self.__bundle) elif not self.vcs.hasSubrepositories(): project = ericApp().getObject("Project") self.vcs.bundleFile = os.path.join( - project.getProjectManagementDir(), "hg-bundle.hg") + project.getProjectManagementDir(), "hg-bundle.hg" + ) if os.path.exists(self.vcs.bundleFile): os.remove(self.vcs.bundleFile) preargs = args[:] preargs.append("--quiet") - preargs.append('--bundle') + preargs.append("--bundle") preargs.append(self.vcs.bundleFile) args.append(self.vcs.bundleFile) if not self.projectMode: args.append(self.__filename) - + if preargs: out, err = self.__hgClient.runcommand(preargs) else: err = "" if err: if ( - self.commandMode == "incoming" and - self.initialCommandMode == "full_log" + self.commandMode == "incoming" + and self.initialCommandMode == "full_log" ): # ignore the error self.commandMode = "log" else: self.__showError(err) elif ( - self.commandMode != "incoming" or - (self.vcs.bundleFile and - os.path.exists(self.vcs.bundleFile)) or - self.__bundle + self.commandMode != "incoming" + or (self.vcs.bundleFile and os.path.exists(self.vcs.bundleFile)) + or self.__bundle ): out, err = self.__hgClient.runcommand(args) self.buf = out.splitlines(True) @@ -1063,17 +1140,16 @@ self.__showError(err) self.__processBuffer() elif ( - self.commandMode == "incoming" and - self.initialCommandMode == "full_log" + self.commandMode == "incoming" and self.initialCommandMode == "full_log" ): # no incoming changesets, just switch to log mode self.commandMode = "log" self.__finish() - + def start(self, name=None, bundle=None, isFile=False, noEntries=0): """ Public slot to start the hg log command. - + @param name file/directory name to show the log for @type str @param bundle name of a bundle file @@ -1085,7 +1161,7 @@ """ self.__bundle = bundle self.__isFile = isFile - + if self.initialCommandMode == "full_log": if isFile: self.commandMode = "log" @@ -1093,59 +1169,57 @@ else: self.commandMode = "incoming" self.__addFinishCallback(self.on_nextButton_clicked) - + self.sbsSelectLabel.clear() - + self.errorGroup.hide() self.errors.clear() QApplication.processEvents() - + self.__initData() - + self.__filename = name - + self.projectMode = name is None self.stopCheckBox.setDisabled(self.projectMode) self.activateWindow() self.raise_() - + self.logTree.clear() self.__started = True self.__identifyProject() self.__getClosedBranches() self.__getHeads() self.__getLogEntries(noEntries=noEntries) - + def __finish(self): """ Private slot called when the process finished or the user pressed the button. """ - self.buttonBox.button( - QDialogButtonBox.StandardButton.Close).setEnabled(True) - self.buttonBox.button( - QDialogButtonBox.StandardButton.Cancel).setEnabled(False) - self.buttonBox.button( - QDialogButtonBox.StandardButton.Close).setDefault(True) - + self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True) + self.refreshButton.setEnabled(True) - + while self.__finishCallbacks: self.__finishCallbacks.pop(0)() - + def __modifyForLargeFiles(self, filename): """ Private method to convert the displayed file name for a large file. - + @param filename file name to be processed (string) @return processed file name (string) """ if filename.startswith((self.LargefilesCacheL, self.LargefilesCacheW)): return self.tr("{0} (large file)").format( - self.PathSeparatorRe.split(filename, 1)[1]) + self.PathSeparatorRe.split(filename, 1)[1] + ) else: return filename - + def __processBuffer(self): """ Private method to process the buffered output of the hg log command. @@ -1170,8 +1244,7 @@ log["author"] = value.strip() elif key == "parents": log["parents"] = [ - int(x.split(":", 1)[0]) - for x in value.strip().split() + int(x.split(":", 1)[0]) for x in value.strip().split() ] elif key == "date": log["date"] = " ".join(value.strip().split()[:2]) @@ -1181,34 +1254,43 @@ if value.strip(): for f in value.strip().split(", "): if f in fileCopies: - changedPaths.append({ - "action": "A", - "path": self.__modifyForLargeFiles(f), - "copyfrom": self.__modifyForLargeFiles( - fileCopies[f]), - }) + changedPaths.append( + { + "action": "A", + "path": self.__modifyForLargeFiles(f), + "copyfrom": self.__modifyForLargeFiles( + fileCopies[f] + ), + } + ) else: - changedPaths.append({ - "action": "A", - "path": self.__modifyForLargeFiles(f), - "copyfrom": "", - }) + changedPaths.append( + { + "action": "A", + "path": self.__modifyForLargeFiles(f), + "copyfrom": "", + } + ) elif key == "files_mods": if value.strip(): for f in value.strip().split(", "): - changedPaths.append({ - "action": "M", - "path": self.__modifyForLargeFiles(f), - "copyfrom": "", - }) + changedPaths.append( + { + "action": "M", + "path": self.__modifyForLargeFiles(f), + "copyfrom": "", + } + ) elif key == "file_dels": if value.strip(): for f in value.strip().split(", "): - changedPaths.append({ - "action": "D", - "path": self.__modifyForLargeFiles(f), - "copyfrom": "", - }) + changedPaths.append( + { + "action": "D", + "path": self.__modifyForLargeFiles(f), + "copyfrom": "", + } + ) elif key == "file_copies": if value.strip(): for entry in value.strip().split(", "): @@ -1231,7 +1313,8 @@ log["latesttag"] = [] elif ":" in tag: log["latesttag"] = [ - t.strip() for t in tag.split(":") if t.strip()] + t.strip() for t in tag.split(":") if t.strip() + ] else: log["latesttag"] = [tag] else: @@ -1242,16 +1325,21 @@ else: if len(log) > 1: self.__generateLogItem( - log["author"], log["date"], - log["message"], log["revision"], changedPaths, - log["parents"], log["branches"], log["tags"], - log["phase"], log["bookmarks"], log["latesttag"], - canPush=canPush) + log["author"], + log["date"], + log["message"], + log["revision"], + changedPaths, + log["parents"], + log["branches"], + log["tags"], + log["phase"], + log["bookmarks"], + log["latesttag"], + canPush=canPush, + ) dt = QDate.fromString(log["date"], Qt.DateFormat.ISODate) - if ( - not self.__maxDate.isValid() and - not self.__minDate.isValid() - ): + if not self.__maxDate.isValid() and not self.__minDate.isValid(): self.__maxDate = dt self.__minDate = dt else: @@ -1263,20 +1351,21 @@ log = {"message": [], "bookmarks": None, "phase": ""} changedPaths = [] fileCopies = {} - + self.__resizeColumnsLog() - + if self.__started and not self.__finishCallbacks: # we are really done if self.__selectedRevisions: foundItems = self.logTree.findItems( - self.__selectedRevisions[0], Qt.MatchFlag.MatchExactly, - self.RevisionColumn) + self.__selectedRevisions[0], + Qt.MatchFlag.MatchExactly, + self.RevisionColumn, + ) if foundItems: self.logTree.setCurrentItem(foundItems[0]) else: - self.logTree.setCurrentItem( - self.logTree.topLevelItem(0)) + self.logTree.setCurrentItem(self.logTree.topLevelItem(0)) elif self.__projectWorkingDirParents: for rev in self.__projectWorkingDirParents: # rev string format must match with the format of the @@ -1284,19 +1373,19 @@ items = self.logTree.findItems( "{0:>7}:".format(rev), Qt.MatchFlag.MatchStartsWith, - self.RevisionColumn) + self.RevisionColumn, + ) if items: self.logTree.setCurrentItem(items[0]) break else: - self.logTree.setCurrentItem( - self.logTree.topLevelItem(0)) + self.logTree.setCurrentItem(self.logTree.topLevelItem(0)) else: self.logTree.setCurrentItem(self.logTree.topLevelItem(0)) self.__started = False - + if self.commandMode in ("incoming", "outgoing"): - self.commandMode = "log" # switch to log mode + self.commandMode = "log" # switch to log mode if self.__lastRev > 0: self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True) @@ -1304,7 +1393,7 @@ if noEntries < self.limitSpinBox.value() and not self.cancelled: self.nextButton.setEnabled(False) self.limitSpinBox.setEnabled(False) - + # update the log filters self.__filterLogsEnabled = False self.fromDate.setMinimumDate(self.__minDate) @@ -1313,59 +1402,56 @@ self.toDate.setMinimumDate(self.__minDate) self.toDate.setMaximumDate(self.__maxDate) self.toDate.setDate(self.__maxDate) - + branchFilter = self.branchCombo.currentText() if not branchFilter: branchFilter = self.__allBranchesFilter self.branchCombo.clear() self.branchCombo.addItems( - [self.__allBranchesFilter] + sorted(self.__branchColors.keys())) - self.branchCombo.setCurrentIndex( - self.branchCombo.findText(branchFilter)) - + [self.__allBranchesFilter] + sorted(self.__branchColors.keys()) + ) + self.branchCombo.setCurrentIndex(self.branchCombo.findText(branchFilter)) + self.__filterLogsEnabled = True if self.__actionMode() == "filter": self.__filterLogs() self.__updateToolMenuActions() - + # restore selected item if self.__selectedRevisions and not self.__finishCallbacks: # we are really done for revision in self.__selectedRevisions: items = self.logTree.findItems( - revision, Qt.MatchFlag.MatchExactly, self.RevisionColumn) + revision, Qt.MatchFlag.MatchExactly, self.RevisionColumn + ) if items: items[0].setSelected(True) self.__selectedRevisions = [] - + def __showError(self, out): """ Private slot to show some error. - + @param out error to be shown (string) """ self.errorGroup.show() self.errors.insertPlainText(out) self.errors.ensureCursorVisible() - + def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. - + @param button button that was clicked (QAbstractButton) """ - if button == self.buttonBox.button( - QDialogButtonBox.StandardButton.Close - ): + if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Close): self.close() - elif button == self.buttonBox.button( - QDialogButtonBox.StandardButton.Cancel - ): + elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel): self.cancelled = True self.__hgClient.cancel() elif button == self.refreshButton: self.on_refreshButton_clicked() - + def __updateSbsSelectLabel(self): """ Private slot to update the enabled status of the diff buttons. @@ -1375,32 +1461,33 @@ selectedItems = self.logTree.selectedItems() if len(selectedItems) == 1: currentItem = selectedItems[0] - rev2 = ( - currentItem.text(self.RevisionColumn).split(":", 1)[0] - .strip() - ) + rev2 = currentItem.text(self.RevisionColumn).split(":", 1)[0].strip() parents = currentItem.data(0, self.__parentsRole) if parents: parentLinks = [] for index in range(len(parents)): parentLinks.append( - '<a href="sbsdiff:{0}_{1}"> {2} </a>' - .format(parents[index], rev2, index + 1)) + '<a href="sbsdiff:{0}_{1}"> {2} </a>'.format( + parents[index], rev2, index + 1 + ) + ) self.sbsSelectLabel.setText( - self.tr('Side-by-Side Diff to Parent {0}').format( - " ".join(parentLinks))) + self.tr("Side-by-Side Diff to Parent {0}").format( + " ".join(parentLinks) + ) + ) elif len(selectedItems) == 2: - rev1 = int(selectedItems[0].text(self.RevisionColumn) - .split(":", 1)[0]) - rev2 = int(selectedItems[1].text(self.RevisionColumn) - .split(":", 1)[0]) + rev1 = int(selectedItems[0].text(self.RevisionColumn).split(":", 1)[0]) + rev2 = int(selectedItems[1].text(self.RevisionColumn).split(":", 1)[0]) if rev1 > rev2: # Swap the entries, so that rev1 < rev2 rev1, rev2 = rev2, rev1 - self.sbsSelectLabel.setText(self.tr( - '<a href="sbsdiff:{0}_{1}">Side-by-Side Compare</a>') - .format(rev1, rev2)) - + self.sbsSelectLabel.setText( + self.tr( + '<a href="sbsdiff:{0}_{1}">Side-by-Side Compare</a>' + ).format(rev1, rev2) + ) + def __updateToolMenuActions(self): """ Private slot to update the status of the tool menu actions and @@ -1412,8 +1499,11 @@ secret = 0 draft = 0 public = 0 - for itm in [item for item in self.logTree.selectedItems() - if not item.data(0, self.__incomingRole)]: + for itm in [ + item + for item in self.logTree.selectedItems() + if not item.data(0, self.__incomingRole) + ]: # count phase for local items only phase = itm.text(self.PhaseColumn) if phase == self.phases["draft"]: @@ -1422,46 +1512,53 @@ secret += 1 else: public += 1 - + # step 2: set the status of the phase action - if ( - public == 0 and - ((secret > 0 and draft == 0) or - (secret == 0 and draft > 0)) + if public == 0 and ( + (secret > 0 and draft == 0) or (secret == 0 and draft > 0) ): self.__phaseAct.setEnabled(True) else: self.__phaseAct.setEnabled(False) - + # do the graft action # step 1: count selected entries not belonging to the # current branch otherBranches = 0 - for itm in [item for item in self.logTree.selectedItems() - if not item.data(0, self.__incomingRole)]: + for itm in [ + item + for item in self.logTree.selectedItems() + if not item.data(0, self.__incomingRole) + ]: # for local items only branch = itm.text(self.BranchColumn) if branch != self.__projectBranch: otherBranches += 1 - + # step 2: set the status of the graft action self.__graftAct.setEnabled(otherBranches > 0) - - selectedItemsCount = len([ - itm for itm in self.logTree.selectedItems() - if not itm.data(0, self.__incomingRole) - ]) - selectedIncomingItemsCount = len([ - itm for itm in self.logTree.selectedItems() - if itm.data(0, self.__incomingRole) - ]) - + + selectedItemsCount = len( + [ + itm + for itm in self.logTree.selectedItems() + if not itm.data(0, self.__incomingRole) + ] + ) + selectedIncomingItemsCount = len( + [ + itm + for itm in self.logTree.selectedItems() + if itm.data(0, self.__incomingRole) + ] + ) + self.__mergeAct.setEnabled(selectedItemsCount == 1) self.__tagAct.setEnabled(selectedItemsCount == 1) self.__switchAct.setEnabled(selectedItemsCount == 1) self.__bookmarkAct.setEnabled(selectedItemsCount == 1) self.__bookmarkMoveAct.setEnabled(selectedItemsCount == 1) - + if selectedIncomingItemsCount > 0: self.__pullAct.setText(self.tr("Pull Selected Changes")) else: @@ -1469,35 +1566,34 @@ if self.vcs.canPull(): self.__pullAct.setEnabled(True) self.__lfPullAct.setEnabled( - self.vcs.isExtensionActive("largefiles") and - selectedItemsCount > 0) + self.vcs.isExtensionActive("largefiles") and selectedItemsCount > 0 + ) else: self.__pullAct.setEnabled(False) self.__lfPullAct.setEnabled(False) - + if self.vcs.canPush(): self.__pushAct.setEnabled( - selectedItemsCount == 1 and - not self.logTree.selectedItems()[0].data( - 0, self.__incomingRole) and - self.logTree.selectedItems()[0].text(self.PhaseColumn) == - self.phases["draft"]) + selectedItemsCount == 1 + and not self.logTree.selectedItems()[0].data(0, self.__incomingRole) + and self.logTree.selectedItems()[0].text(self.PhaseColumn) + == self.phases["draft"] + ) self.__pushAllAct.setEnabled(True) else: self.__pushAct.setEnabled(False) self.__pushAllAct.setEnabled(False) - + self.__stripAct.setEnabled( - self.vcs.isExtensionActive("strip") and - selectedItemsCount == 1) - + self.vcs.isExtensionActive("strip") and selectedItemsCount == 1 + ) + # count incoming items for 'full_log' if self.initialCommandMode == "full_log": # incoming items are at the top incomingCount = 0 for row in range(self.logTree.topLevelItemCount()): - if self.logTree.topLevelItem(row).data( - 0, self.__incomingRole): + if self.logTree.topLevelItem(row).data(0, self.__incomingRole): incomingCount += 1 else: break @@ -1506,76 +1602,103 @@ localCount = self.logTree.topLevelItemCount() self.__bundleAct.setEnabled(localCount > 0) self.__unbundleAct.setEnabled(False) - + self.__gpgSignAct.setEnabled( - self.vcs.isExtensionActive("gpg") and - selectedItemsCount > 0) + self.vcs.isExtensionActive("gpg") and selectedItemsCount > 0 + ) self.__gpgVerifyAct.setEnabled( - self.vcs.isExtensionActive("gpg") and - selectedItemsCount == 1) - + self.vcs.isExtensionActive("gpg") and selectedItemsCount == 1 + ) + if self.vcs.isExtensionActive("closehead"): - revs = [itm.text(self.RevisionColumn).strip().split(":", 1)[0] - for itm in self.logTree.selectedItems() - if not itm.data(0, self.__incomingRole)] + revs = [ + itm.text(self.RevisionColumn).strip().split(":", 1)[0] + for itm in self.logTree.selectedItems() + if not itm.data(0, self.__incomingRole) + ] revs = [rev for rev in revs if rev in self.__headRevisions] self.__closeHeadsAct.setEnabled(len(revs) > 0) else: self.__closeHeadsAct.setEnabled(False) self.actionsButton.setEnabled(True) - + elif self.initialCommandMode == "incoming" and self.projectMode: - for act in [self.__phaseAct, self.__graftAct, self.__mergeAct, - self.__tagAct, self.__closeHeadsAct, self.__switchAct, - self.__bookmarkAct, self.__bookmarkMoveAct, - self.__pushAct, self.__pushAllAct, self.__stripAct, - self.__bundleAct, self.__gpgSignAct, - self.__gpgVerifyAct]: + for act in [ + self.__phaseAct, + self.__graftAct, + self.__mergeAct, + self.__tagAct, + self.__closeHeadsAct, + self.__switchAct, + self.__bookmarkAct, + self.__bookmarkMoveAct, + self.__pushAct, + self.__pushAllAct, + self.__stripAct, + self.__bundleAct, + self.__gpgSignAct, + self.__gpgVerifyAct, + ]: act.setEnabled(False) - + self.__pullAct.setText(self.tr("Pull Selected Changes")) if self.vcs.canPull() and not bool(self.__bundle): - selectedIncomingItemsCount = len([ - itm for itm in self.logTree.selectedItems() - if itm.data(0, self.__incomingRole) - ]) + selectedIncomingItemsCount = len( + [ + itm + for itm in self.logTree.selectedItems() + if itm.data(0, self.__incomingRole) + ] + ) self.__pullAct.setEnabled(selectedIncomingItemsCount > 0) self.__lfPullAct.setEnabled( - self.vcs.isExtensionActive("largefiles") and - selectedIncomingItemsCount > 0) + self.vcs.isExtensionActive("largefiles") + and selectedIncomingItemsCount > 0 + ) else: self.__pullAct.setEnabled(False) self.__lfPullAct.setEnabled(False) - + self.__unbundleAct.setEnabled(bool(self.__bundle)) - + self.actionsButton.setEnabled(True) - + elif self.initialCommandMode == "outgoing" and self.projectMode: - for act in [self.__phaseAct, self.__graftAct, self.__mergeAct, - self.__tagAct, self.__closeHeadsAct, self.__switchAct, - self.__bookmarkAct, self.__bookmarkMoveAct, - self.__pullAct, self.__lfPullAct, - self.__stripAct, self.__gpgSignAct, - self.__gpgVerifyAct, self.__unbundleAct]: + for act in [ + self.__phaseAct, + self.__graftAct, + self.__mergeAct, + self.__tagAct, + self.__closeHeadsAct, + self.__switchAct, + self.__bookmarkAct, + self.__bookmarkMoveAct, + self.__pullAct, + self.__lfPullAct, + self.__stripAct, + self.__gpgSignAct, + self.__gpgVerifyAct, + self.__unbundleAct, + ]: act.setEnabled(False) - + selectedItemsCount = len(self.logTree.selectedItems()) if self.vcs.canPush(): self.__pushAct.setEnabled( - selectedItemsCount == 1 and - self.logTree.selectedItems()[0].text(self.PhaseColumn) == - self.phases["draft"]) + selectedItemsCount == 1 + and self.logTree.selectedItems()[0].text(self.PhaseColumn) + == self.phases["draft"] + ) self.__pushAllAct.setEnabled(True) else: self.__pushAct.setEnabled(False) self.__pushAllAct.setEnabled(False) - + self.__bundleAct.setEnabled(selectedItemsCount > 0) - + else: self.actionsButton.setEnabled(False) - + def __updateDetailsAndFiles(self): """ Private slot to update the details and file changes panes. @@ -1583,11 +1706,10 @@ self.detailsEdit.clear() self.filesTree.clear() self.__diffUpdatesFiles = False - + selectedItems = self.logTree.selectedItems() if len(selectedItems) == 1: - self.detailsEdit.setHtml( - self.__generateDetailsTableText(selectedItems[0])) + self.detailsEdit.setHtml(self.__generateDetailsTableText(selectedItems[0])) self.__updateFilesTree(self.filesTree, selectedItems[0]) self.__resizeColumnsFiles() self.__resortFiles() @@ -1598,7 +1720,8 @@ if index1 > index2: # Swap the entries selectedItems[0], selectedItems[1] = ( - selectedItems[1], selectedItems[0] + selectedItems[1], + selectedItems[0], ) html = "{0}<hr/>{1}".format( self.__generateDetailsTableText(selectedItems[0]), @@ -1606,12 +1729,12 @@ ) self.detailsEdit.setHtml(html) # self.filesTree is updated by the diff - + def __generateDetailsTableText(self, itm): """ Private method to generate an HTML table with the details of the given changeset. - + @param itm reference to the item the table should be based on @type QTreeWidgetItem @return HTML table containing details @@ -1622,76 +1745,74 @@ tagsStr = self.__tagsTemplate.format(itm.text(self.TagsColumn)) else: tagsStr = "" - + if itm.text(self.BookmarksColumn): bookmarksStr = self.__bookmarksTemplate.format( - itm.text(self.BookmarksColumn)) + itm.text(self.BookmarksColumn) + ) else: bookmarksStr = "" - + if self.projectMode and itm.data(0, self.__latestTagRole): latestTagLinks = [] for tag in itm.data(0, self.__latestTagRole): - latestTagLinks.append('<a href="rev:{0}">{1}</a>'.format( - self.__getRevisionOfTag(tag)[0], tag)) + latestTagLinks.append( + '<a href="rev:{0}">{1}</a>'.format( + self.__getRevisionOfTag(tag)[0], tag + ) + ) latestTagStr = self.__latestTagTemplate.format( - ", ".join(latestTagLinks)) + ", ".join(latestTagLinks) + ) else: latestTagStr = "" - + rev = int(itm.text(self.RevisionColumn).split(":", 1)[0]) - + if itm.data(0, self.__parentsRole): parentLinks = [] - for parent in [str(x) for x in - itm.data(0, self.__parentsRole)]: - parentLinks.append( - '<a href="rev:{0}">{0}</a>'.format(parent)) - parentsStr = self.__parentsTemplate.format( - ", ".join(parentLinks)) + for parent in [str(x) for x in itm.data(0, self.__parentsRole)]: + parentLinks.append('<a href="rev:{0}">{0}</a>'.format(parent)) + parentsStr = self.__parentsTemplate.format(", ".join(parentLinks)) else: parentsStr = "" - + if self.__childrenInfo[rev]: childLinks = [] for child in [str(x) for x in self.__childrenInfo[rev]]: - childLinks.append( - '<a href="rev:{0}">{0}</a>'.format(child)) - childrenStr = self.__childrenTemplate.format( - ", ".join(childLinks)) + childLinks.append('<a href="rev:{0}">{0}</a>'.format(child)) + childrenStr = self.__childrenTemplate.format(", ".join(childLinks)) else: childrenStr = "" - + messagesList = [] for line in itm.data(0, self.__messageRole): match = HgLogBrowserDialog.GraftedRe.fullmatch(line) if match: messagesList.append( - HgLogBrowserDialog.GraftedTemplate.format( - match.group(1))) + HgLogBrowserDialog.GraftedTemplate.format(match.group(1)) + ) else: messagesList.append(Utilities.html_encode(line.strip())) messageStr = "<br />\n".join(messagesList) - + html = self.__detailsTemplate.format( itm.text(self.RevisionColumn), itm.text(self.DateColumn), itm.text(self.AuthorColumn), - itm.text(self.BranchColumn).replace( - self.ClosedIndicator, ""), - parentsStr + childrenStr + tagsStr + latestTagStr + - bookmarksStr, + itm.text(self.BranchColumn).replace(self.ClosedIndicator, ""), + parentsStr + childrenStr + tagsStr + latestTagStr + bookmarksStr, messageStr, ) else: html = "" - + return html - + def __updateFilesTree(self, parent, itm): """ Private method to update the files tree with changes of the given item. - + @param parent parent for the items to be added @type QTreeWidget or QTreeWidgetItem @param itm reference to the item the update should be based on @@ -1701,38 +1822,44 @@ changes = itm.data(0, self.__changesRole) if len(changes) > 0: for change in changes: - QTreeWidgetItem(parent, [ - self.flags[change["action"]], - change["path"].strip(), - change["copyfrom"].strip(), - ]) - + QTreeWidgetItem( + parent, + [ + self.flags[change["action"]], + change["path"].strip(), + change["copyfrom"].strip(), + ], + ) + @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) def on_logTree_currentItemChanged(self, current, previous): """ Private slot called, when the current item of the log tree changes. - + @param current reference to the new current item (QTreeWidgetItem) @param previous reference to the old current item (QTreeWidgetItem) """ self.__updateToolMenuActions() - + # Highlight the current entry using a bold font for col in range(self.logTree.columnCount()): current and current.setFont(col, self.__logTreeBoldFont) previous and previous.setFont(col, self.__logTreeNormalFont) - + # set the state of the up and down buttons self.upButton.setEnabled( - current is not None and - self.logTree.indexOfTopLevelItem(current) > 0) + current is not None and self.logTree.indexOfTopLevelItem(current) > 0 + ) self.downButton.setEnabled( - current is not None and - int(current.text(self.RevisionColumn).split(":")[0]) > 0 and - (self.logTree.indexOfTopLevelItem(current) < - self.logTree.topLevelItemCount() - 1 or - self.nextButton.isEnabled())) - + current is not None + and int(current.text(self.RevisionColumn).split(":")[0]) > 0 + and ( + self.logTree.indexOfTopLevelItem(current) + < self.logTree.topLevelItemCount() - 1 + or self.nextButton.isEnabled() + ) + ) + @pyqtSlot() def on_logTree_itemSelectionChanged(self): """ @@ -1742,7 +1869,7 @@ self.__updateSbsSelectLabel() self.__updateToolMenuActions() self.__generateDiffs() - + @pyqtSlot() def on_upButton_clicked(self): """ @@ -1751,7 +1878,7 @@ itm = self.logTree.itemAbove(self.logTree.currentItem()) if itm: self.logTree.setCurrentItem(itm) - + @pyqtSlot() def on_downButton_clicked(self): """ @@ -1765,7 +1892,7 @@ if self.nextButton.isEnabled(): self.__addFinishCallback(self.on_downButton_clicked) self.on_nextButton_clicked() - + @pyqtSlot() def on_nextButton_clicked(self): """ @@ -1776,61 +1903,61 @@ self.__getLogEntries(startRev=self.__lastRev - 1) else: self.__getLogEntries() - + @pyqtSlot(QDate) def on_fromDate_dateChanged(self, date): """ Private slot called, when the from date changes. - + @param date new date (QDate) """ if self.__actionMode() == "filter": self.__filterLogs() - + @pyqtSlot(QDate) def on_toDate_dateChanged(self, date): """ Private slot called, when the from date changes. - + @param date new date (QDate) """ if self.__actionMode() == "filter": self.__filterLogs() - + @pyqtSlot(int) def on_branchCombo_activated(self, index): """ Private slot called, when a new branch is selected. - + @param index index of the selected entry @type int """ if self.__actionMode() == "filter": self.__filterLogs() - + @pyqtSlot(int) def on_fieldCombo_activated(self, index): """ Private slot called, when a new filter field is selected. - + @param index index of the selected entry @type int """ if self.__actionMode() == "filter": self.__filterLogs() - + @pyqtSlot(str) def on_rxEdit_textChanged(self, txt): """ Private slot called, when a filter expression is entered. - + @param txt filter expression (string) """ if self.__actionMode() == "filter": self.__filterLogs() elif self.__actionMode() == "find": self.__findItem(self.__findBackwards, interactive=True) - + @pyqtSlot() def on_rxEdit_returnPressed(self): """ @@ -1838,7 +1965,7 @@ """ if self.__actionMode() == "find": self.__findItem(self.__findBackwards, interactive=True) - + def __filterLogs(self): """ Private method to filter the log entries. @@ -1847,9 +1974,9 @@ from_ = self.fromDate.date().toString("yyyy-MM-dd") to_ = self.toDate.date().addDays(1).toString("yyyy-MM-dd") branch = self.branchCombo.currentText() - closedBranch = branch + '--' + closedBranch = branch + "--" fieldIndex, searchRx, indexIsRole = self.__prepareFieldSearch() - + visibleItemCount = self.logTree.topLevelItemCount() currentItem = self.logTree.currentItem() for topIndex in range(self.logTree.topLevelItemCount()): @@ -1858,8 +1985,8 @@ if fieldIndex == self.__changesRole: changes = topItem.data(0, self.__changesRole) txt = "\n".join( - [c["path"] for c in changes] + - [c["copyfrom"] for c in changes] + [c["path"] for c in changes] + + [c["copyfrom"] for c in changes] ) else: # Find based on complete message text @@ -1867,12 +1994,13 @@ else: txt = topItem.text(fieldIndex) if ( - topItem.text(self.DateColumn) <= to_ and - topItem.text(self.DateColumn) >= from_ and - (branch == self.__allBranchesFilter or - topItem.text(self.BranchColumn) in - [branch, closedBranch]) and - searchRx.search(txt) is not None + topItem.text(self.DateColumn) <= to_ + and topItem.text(self.DateColumn) >= from_ + and ( + branch == self.__allBranchesFilter + or topItem.text(self.BranchColumn) in [branch, closedBranch] + ) + and searchRx.search(txt) is not None ): topItem.setHidden(False) if topItem is currentItem: @@ -1883,13 +2011,13 @@ self.filesTree.clear() visibleItemCount -= 1 self.logTree.header().setSectionHidden( - self.IconColumn, - visibleItemCount != self.logTree.topLevelItemCount()) - + self.IconColumn, visibleItemCount != self.logTree.topLevelItemCount() + ) + def __prepareFieldSearch(self): """ Private slot to prepare the filed search data. - + @return tuple of field index, search expression and flag indicating that the field index is a data role (integer, string, boolean) """ @@ -1902,8 +2030,7 @@ fieldIndex = self.RevisionColumn txt = self.rxEdit.text() if txt.startswith("^"): - searchRx = re.compile(r"^\s*{0}".format(txt[1:]), - re.IGNORECASE) + searchRx = re.compile(r"^\s*{0}".format(txt[1:]), re.IGNORECASE) else: searchRx = re.compile(txt, re.IGNORECASE) elif txt == "file": @@ -1917,44 +2044,42 @@ fieldIndex = self.__messageRole searchRx = re.compile(self.rxEdit.text(), re.IGNORECASE) indexIsRole = True - + return fieldIndex, searchRx, indexIsRole - + @pyqtSlot(bool) def on_stopCheckBox_clicked(self, checked): """ Private slot called, when the stop on copy/move checkbox is clicked. - + @param checked flag indicating the state of the check box (boolean) """ - self.vcs.getPlugin().setPreferences("StopLogOnCopy", - self.stopCheckBox.isChecked()) + self.vcs.getPlugin().setPreferences( + "StopLogOnCopy", self.stopCheckBox.isChecked() + ) self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True) - + @pyqtSlot() def on_refreshButton_clicked(self, addNext=False): """ Private slot to refresh the log. - + @param addNext flag indicating to get a second batch of log entries as well @type bool """ - self.buttonBox.button( - QDialogButtonBox.StandardButton.Close).setEnabled(False) - self.buttonBox.button( - QDialogButtonBox.StandardButton.Cancel).setEnabled(True) - self.buttonBox.button( - QDialogButtonBox.StandardButton.Cancel).setDefault(True) - + self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True) + self.refreshButton.setEnabled(False) - + # save the selected items commit IDs self.__selectedRevisions = [] for item in self.logTree.selectedItems(): self.__selectedRevisions.append(item.text(self.RevisionColumn)) - + if self.initialCommandMode in ("incoming", "outgoing"): self.nextButton.setEnabled(False) self.limitSpinBox.setEnabled(False) @@ -1963,15 +2088,19 @@ else: self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True) - + if self.initialCommandMode == "full_log": self.commandMode = "incoming" self.__addFinishCallback(self.on_nextButton_clicked) else: self.commandMode = self.initialCommandMode - self.start(self.__filename, bundle=self.__bundle, isFile=self.__isFile, - noEntries=self.logTree.topLevelItemCount()) - + self.start( + self.__filename, + bundle=self.__bundle, + isFile=self.__isFile, + noEntries=self.logTree.topLevelItemCount(), + ) + @pyqtSlot() def __phaseActTriggered(self): """ @@ -1983,13 +2112,12 @@ revs = [] for itm in self.logTree.selectedItems(): if itm.text(self.PhaseColumn) == currentPhase: - revs.append( - itm.text(self.RevisionColumn).split(":")[0].strip()) - + revs.append(itm.text(self.RevisionColumn).split(":")[0].strip()) + if not revs: self.__phaseAct.setEnabled(False) return - + if currentPhase == self.phases["draft"]: newPhase = self.phases["secret"] data = (revs, "s", True) @@ -2000,88 +2128,113 @@ if res: for itm in self.logTree.selectedItems(): itm.setText(self.PhaseColumn, newPhase) - + @pyqtSlot() def __graftActTriggered(self): """ Private slot to handle the Copy Changesets action. """ revs = [] - - for itm in [item for item in self.logTree.selectedItems() - if not item.data(0, self.__incomingRole)]: + + for itm in [ + item + for item in self.logTree.selectedItems() + if not item.data(0, self.__incomingRole) + ]: branch = itm.text(self.BranchColumn) if branch != self.__projectBranch: - revs.append( - itm.text(self.RevisionColumn).strip().split(":", 1)[0]) - + revs.append(itm.text(self.RevisionColumn).strip().split(":", 1)[0]) + if revs: shouldReopen = self.vcs.hgGraft(revs) if shouldReopen: res = EricMessageBox.yesNo( None, self.tr("Copy Changesets"), - self.tr( - """The project should be reread. Do this now?"""), - yesDefault=True) + self.tr("""The project should be reread. Do this now?"""), + yesDefault=True, + ) if res: ericApp().getObject("Project").reopenProject() return - + self.on_refreshButton_clicked() - + @pyqtSlot() def __tagActTriggered(self): """ Private slot to tag the selected revision. """ - if len([itm for itm in self.logTree.selectedItems() - if not itm.data(0, self.__incomingRole)]) == 1: + if ( + len( + [ + itm + for itm in self.logTree.selectedItems() + if not itm.data(0, self.__incomingRole) + ] + ) + == 1 + ): itm = self.logTree.selectedItems()[0] rev = itm.text(self.RevisionColumn).strip().split(":", 1)[0] tag = itm.text(self.TagsColumn).strip().split(", ", 1)[0] res = self.vcs.vcsTag(revision=rev, tagName=tag) if res: self.on_refreshButton_clicked() - + @pyqtSlot() def __closeHeadsActTriggered(self): """ Private slot to close the selected head revisions. """ if self.vcs.isExtensionActive("closehead"): - revs = [itm.text(self.RevisionColumn).strip().split(":", 1)[0] - for itm in self.logTree.selectedItems() - if not itm.data(0, self.__incomingRole)] + revs = [ + itm.text(self.RevisionColumn).strip().split(":", 1)[0] + for itm in self.logTree.selectedItems() + if not itm.data(0, self.__incomingRole) + ] revs = [rev for rev in revs if rev in self.__headRevisions] if revs: closeheadExtension = self.vcs.getExtensionObject("closehead") if closeheadExtension is not None: closeheadExtension.hgCloseheads(revisions=revs) - + self.on_refreshButton_clicked() - + @pyqtSlot() def __switchActTriggered(self): """ Private slot to switch the working directory to the selected revision. """ - if len([itm for itm in self.logTree.selectedItems() - if not itm.data(0, self.__incomingRole)]) == 1: + if ( + len( + [ + itm + for itm in self.logTree.selectedItems() + if not itm.data(0, self.__incomingRole) + ] + ) + == 1 + ): itm = self.logTree.selectedItems()[0] rev = itm.text(self.RevisionColumn).strip().split(":", 1)[0] - bookmarks = [bm.strip() for bm in - itm.text(self.BookmarksColumn).strip().split(",") - if bm.strip()] + bookmarks = [ + bm.strip() + for bm in itm.text(self.BookmarksColumn).strip().split(",") + if bm.strip() + ] if bookmarks: bookmark, ok = QInputDialog.getItem( self, self.tr("Switch"), - self.tr("Select bookmark to switch to (leave empty to" - " use revision):"), + self.tr( + "Select bookmark to switch to (leave empty to" " use revision):" + ), [""] + bookmarks, - 0, False) + 0, + False, + ) if not ok: return if bookmark: @@ -2092,78 +2245,96 @@ res = EricMessageBox.yesNo( None, self.tr("Switch"), - self.tr( - """The project should be reread. Do this now?"""), - yesDefault=True) + self.tr("""The project should be reread. Do this now?"""), + yesDefault=True, + ) if res: ericApp().getObject("Project").reopenProject() return - + self.on_refreshButton_clicked() - + @pyqtSlot() def __bookmarkActTriggered(self): """ Private slot to bookmark the selected revision. """ - if len([itm for itm in self.logTree.selectedItems() - if not itm.data(0, self.__incomingRole)]) == 1: + if ( + len( + [ + itm + for itm in self.logTree.selectedItems() + if not itm.data(0, self.__incomingRole) + ] + ) + == 1 + ): itm = self.logTree.selectedItems()[0] - rev, changeset = ( - itm.text(self.RevisionColumn).strip().split(":", 1) - ) + rev, changeset = itm.text(self.RevisionColumn).strip().split(":", 1) bookmark, ok = QInputDialog.getText( self, self.tr("Define Bookmark"), - self.tr('Enter bookmark name for changeset "{0}":').format( - changeset), - QLineEdit.EchoMode.Normal) + self.tr('Enter bookmark name for changeset "{0}":').format(changeset), + QLineEdit.EchoMode.Normal, + ) if ok and bool(bookmark): self.vcs.hgBookmarkDefine( - revision="rev({0})".format(rev), - bookmark=bookmark) + revision="rev({0})".format(rev), bookmark=bookmark + ) self.on_refreshButton_clicked() - + @pyqtSlot() def __bookmarkMoveActTriggered(self): """ Private slot to move a bookmark to the selected revision. """ - if len([itm for itm in self.logTree.selectedItems() - if not itm.data(0, self.__incomingRole)]) == 1: + if ( + len( + [ + itm + for itm in self.logTree.selectedItems() + if not itm.data(0, self.__incomingRole) + ] + ) + == 1 + ): itm = self.logTree.selectedItems()[0] - rev, changeset = ( - itm.text(self.RevisionColumn).strip().split(":", 1) - ) + rev, changeset = itm.text(self.RevisionColumn).strip().split(":", 1) bookmarksList = self.vcs.hgGetBookmarksList() bookmark, ok = QInputDialog.getItem( self, self.tr("Move Bookmark"), - self.tr('Select the bookmark to be moved to changeset' - ' "{0}":').format(changeset), + self.tr( + "Select the bookmark to be moved to changeset" ' "{0}":' + ).format(changeset), [""] + bookmarksList, - 0, False) + 0, + False, + ) if ok and bool(bookmark): self.vcs.hgBookmarkMove( - revision="rev({0})".format(rev), - bookmark=bookmark) + revision="rev({0})".format(rev), bookmark=bookmark + ) self.on_refreshButton_clicked() - + @pyqtSlot() def __lfPullActTriggered(self): """ Private slot to pull large files of selected revisions. """ revs = [] - for itm in [item for item in self.logTree.selectedItems() - if not item.data(0, self.__incomingRole)]: + for itm in [ + item + for item in self.logTree.selectedItems() + if not item.data(0, self.__incomingRole) + ]: rev = itm.text(self.RevisionColumn).strip().split(":", 1)[0] if rev: revs.append(rev) - + if revs: self.vcs.getExtensionObject("largefiles").hgLfPull(revisions=revs) - + @pyqtSlot() def __pullActTriggered(self): """ @@ -2172,11 +2343,14 @@ shouldReopen = False refresh = False addNext = False - + if self.initialCommandMode in ("log", "full_log", "incoming"): revs = [] - for itm in [item for item in self.logTree.selectedItems() - if item.data(0, self.__incomingRole)]: + for itm in [ + item + for item in self.logTree.selectedItems() + if item.data(0, self.__incomingRole) + ]: rev = itm.text(self.RevisionColumn).split(":")[1].strip() if rev: revs.append(rev) @@ -2184,21 +2358,21 @@ refresh = True if self.initialCommandMode == "incoming": addNext = True - + if shouldReopen: res = EricMessageBox.yesNo( None, self.tr("Pull Changes"), - self.tr( - """The project should be reread. Do this now?"""), - yesDefault=True) + self.tr("""The project should be reread. Do this now?"""), + yesDefault=True, + ) if res: ericApp().getObject("Project").reopenProject() return - + if refresh: self.on_refreshButton_clicked(addNext=addNext) - + @pyqtSlot() def __pushActTriggered(self): """ @@ -2211,8 +2385,9 @@ if rev: self.vcs.hgPush(rev=rev) self.on_refreshButton_clicked( - addNext=self.initialCommandMode == "outgoing") - + addNext=self.initialCommandMode == "outgoing" + ) + @pyqtSlot() def __pushAllActTriggered(self): """ @@ -2220,7 +2395,7 @@ """ self.vcs.hgPush() self.on_refreshButton_clicked() - + @pyqtSlot() def __stripActTriggered(self): """ @@ -2229,21 +2404,20 @@ itm = self.logTree.selectedItems()[0] if not itm.data(0, self.__incomingRole): rev = itm.text(self.RevisionColumn).strip().split(":", 1)[1] - shouldReopen = self.vcs.getExtensionObject("strip").hgStrip( - rev=rev) + shouldReopen = self.vcs.getExtensionObject("strip").hgStrip(rev=rev) if shouldReopen: res = EricMessageBox.yesNo( None, self.tr("Strip Changesets"), - self.tr( - """The project should be reread. Do this now?"""), - yesDefault=True) + self.tr("""The project should be reread. Do this now?"""), + yesDefault=True, + ) if res: ericApp().getObject("Project").reopenProject() return - + self.on_refreshButton_clicked() - + @pyqtSlot() def __mergeActTriggered(self): """ @@ -2253,17 +2427,21 @@ itm = self.logTree.selectedItems()[0] if not itm.data(0, self.__incomingRole): rev = "rev({0})".format( - itm.text(self.RevisionColumn).strip().split(":", 1)[0]) + itm.text(self.RevisionColumn).strip().split(":", 1)[0] + ) self.vcs.vcsMerge("", rev=rev) - + @pyqtSlot() def __bundleActTriggered(self): """ Private slot to create a changegroup file. """ if self.initialCommandMode in ("log", "full_log"): - selectedItems = [itm for itm in self.logTree.selectedItems() - if not itm.data(0, self.__incomingRole)] + selectedItems = [ + itm + for itm in self.logTree.selectedItems() + if not itm.data(0, self.__incomingRole) + ] if len(selectedItems) == 0: # all revisions of the local repository will be bundled bundleData = { @@ -2273,8 +2451,9 @@ } elif len(selectedItems) == 1: # the selected changeset is the base - rev = selectedItems[0].text(self.RevisionColumn).split( - ":", 1)[0].strip() + rev = ( + selectedItems[0].text(self.RevisionColumn).split(":", 1)[0].strip() + ) bundleData = { "revs": [], "base": rev, @@ -2290,7 +2469,7 @@ baseRev = min(revs) while baseRev in revs: revs.remove(baseRev) - + bundleData = { "revs": [str(rev) for rev in revs], "base": str(baseRev), @@ -2303,15 +2482,15 @@ for itm in selectedItems: rev = itm.text(self.RevisionColumn).split(":", 1)[0] revs.append(rev.strip()) - + bundleData = { "revs": revs, "base": "", "all": False, } - + self.vcs.hgBundle(bundleData=bundleData) - + @pyqtSlot() def __unbundleActTriggered(self): """ @@ -2324,29 +2503,33 @@ None, self.tr("Apply Changegroup"), self.tr("""The project should be reread. Do this now?"""), - yesDefault=True) + yesDefault=True, + ) if res: ericApp().getObject("Project").reopenProject() return - + self.vcs.vcsLogBrowser() self.close() - + @pyqtSlot() def __gpgSignActTriggered(self): """ Private slot to sign the selected revisions. """ revs = [] - for itm in [item for item in self.logTree.selectedItems() - if not item.data(0, self.__incomingRole)]: + for itm in [ + item + for item in self.logTree.selectedItems() + if not item.data(0, self.__incomingRole) + ]: rev = itm.text(self.RevisionColumn).split(":", 1)[0].strip() if rev: revs.append(rev) - + if revs: self.vcs.getExtensionObject("gpg").hgGpgSign(revisions=revs) - + @pyqtSlot() def __gpgVerifyActTriggered(self): """ @@ -2356,13 +2539,12 @@ if not itm.data(0, self.__incomingRole): rev = itm.text(self.RevisionColumn).split(":", 1)[0].strip() if rev: - self.vcs.getExtensionObject("gpg").hgGpgVerifySignatures( - rev=rev) - + self.vcs.getExtensionObject("gpg").hgGpgVerifySignatures(rev=rev) + def __selectAllActTriggered(self, select=True): """ Private method to select or unselect all log entries. - + @param select flag indicating to select all entries @type bool """ @@ -2371,66 +2553,64 @@ self.logTree.topLevelItem(row).setSelected(select) self.logTree.blockSignals(blocked) self.on_logTree_itemSelectionChanged() - + def __actionMode(self): """ Private method to get the selected action mode. - + @return selected action mode (string, one of filter or find) """ - return self.modeComboBox.itemData( - self.modeComboBox.currentIndex()) - + return self.modeComboBox.itemData(self.modeComboBox.currentIndex()) + @pyqtSlot(int) def on_modeComboBox_currentIndexChanged(self, index): """ Private slot to react on mode changes. - + @param index index of the selected entry (integer) """ mode = self.modeComboBox.itemData(index) findMode = mode == "find" filterMode = mode == "filter" - + self.fromDate.setEnabled(filterMode) self.toDate.setEnabled(filterMode) self.branchCombo.setEnabled(filterMode) self.findPrevButton.setVisible(findMode) self.findNextButton.setVisible(findMode) - + if findMode: for topIndex in range(self.logTree.topLevelItemCount()): self.logTree.topLevelItem(topIndex).setHidden(False) self.logTree.header().setSectionHidden(self.IconColumn, False) elif filterMode: self.__filterLogs() - + @pyqtSlot() def on_findPrevButton_clicked(self): """ Private slot to find the previous item matching the entered criteria. """ self.__findItem(True) - + @pyqtSlot() def on_findNextButton_clicked(self): """ Private slot to find the next item matching the entered criteria. """ self.__findItem(False) - + def __findItem(self, backwards=False, interactive=False): """ Private slot to find an item matching the entered criteria. - + @param backwards flag indicating to search backwards (boolean) @param interactive flag indicating an interactive search (boolean) """ self.__findBackwards = backwards - + fieldIndex, searchRx, indexIsRole = self.__prepareFieldSearch() - currentIndex = self.logTree.indexOfTopLevelItem( - self.logTree.currentItem()) + currentIndex = self.logTree.indexOfTopLevelItem(self.logTree.currentItem()) if backwards: if interactive: indexes = range(currentIndex, -1, -1) @@ -2440,17 +2620,15 @@ if interactive: indexes = range(currentIndex, self.logTree.topLevelItemCount()) else: - indexes = range(currentIndex + 1, - self.logTree.topLevelItemCount()) - + indexes = range(currentIndex + 1, self.logTree.topLevelItemCount()) + for index in indexes: topItem = self.logTree.topLevelItem(index) if indexIsRole: if fieldIndex == self.__changesRole: changes = topItem.data(0, self.__changesRole) txt = "\n".join( - [c["path"] for c in changes] + - [c["copyfrom"] for c in changes] + [c["path"] for c in changes] + [c["copyfrom"] for c in changes] ) else: # Find based on complete message text @@ -2464,13 +2642,14 @@ EricMessageBox.information( self, self.tr("Find Commit"), - self.tr("""'{0}' was not found.""").format(self.rxEdit.text())) - + self.tr("""'{0}' was not found.""").format(self.rxEdit.text()), + ) + def __revisionClicked(self, url): """ Private slot to handle the anchorClicked signal of the changeset details pane. - + @param url URL that was clicked @type QUrl """ @@ -2487,8 +2666,7 @@ searchStr = ":{0}".format(changeset[:12]) # max. 12 hash chars # format must be in sync with item generation format searchFlags = Qt.MatchFlag.MatchContains - items = self.logTree.findItems( - searchStr, searchFlags, self.RevisionColumn) + items = self.logTree.findItems(searchStr, searchFlags, self.RevisionColumn) if items: itm = items[0] if itm.isHidden(): @@ -2497,18 +2675,17 @@ else: # load the next batch and try again if not self.cancelled and self.nextButton.isEnabled(): - self.__addFinishCallback( - lambda: self.__revisionClicked(url)) + self.__addFinishCallback(lambda: self.__revisionClicked(url)) self.on_nextButton_clicked() - + ########################################################################### ## Diff handling methods below ########################################################################### - + def __generateDiffs(self, parent=1): """ Private slot to generate diff outputs for the selected item. - + @param parent number of parent to diff against @type int """ @@ -2516,7 +2693,7 @@ self.diffLabel.setText(self.tr("Differences")) self.diffSelectLabel.clear() self.diffHighlighter.regenerateRules() - + selectedItems = self.logTree.selectedItems() if len(selectedItems) == 1: currentItem = selectedItems[0] @@ -2524,12 +2701,12 @@ parents = currentItem.data(0, self.__parentsRole) if len(parents) >= parent: self.diffLabel.setText( - self.tr("Differences to Parent {0}").format(parent)) + self.tr("Differences to Parent {0}").format(parent) + ) rev1 = parents[parent - 1] - - self.__diffGenerator.start(self.__filename, [rev1, rev2], - self.__bundle) - + + self.__diffGenerator.start(self.__filename, [rev1, rev2], self.__bundle) + if len(parents) > 1: if parent == 1: par1 = " 1 " @@ -2540,34 +2717,33 @@ else: par2 = '<a href="diff:2"> 2 </a>' self.diffSelectLabel.setText( - self.tr('Diff to Parent {0}{1}').format(par1, par2)) + self.tr("Diff to Parent {0}{1}").format(par1, par2) + ) elif len(selectedItems) == 2: - rev2 = int(selectedItems[0].text( - self.RevisionColumn).split(":")[0]) - rev1 = int(selectedItems[1].text( - self.RevisionColumn).split(":")[0]) - - self.__diffGenerator.start(self.__filename, - [min(rev1, rev2), max(rev1, rev2)], - self.__bundle) + rev2 = int(selectedItems[0].text(self.RevisionColumn).split(":")[0]) + rev1 = int(selectedItems[1].text(self.RevisionColumn).split(":")[0]) + + self.__diffGenerator.start( + self.__filename, [min(rev1, rev2), max(rev1, rev2)], self.__bundle + ) else: self.diffEdit.clear() - + def __generatorFinished(self): """ Private slot connected to the finished signal of the diff generator. """ diff, errors, fileSeparators = self.__diffGenerator.getResult() - + if diff: self.diffEdit.setPlainText("".join(diff)) elif errors: self.diffEdit.setPlainText("".join(errors)) else: - self.diffEdit.setPlainText(self.tr('There is no difference.')) - + self.diffEdit.setPlainText(self.tr("There is no difference.")) + self.saveLabel.setVisible(bool(diff)) - + if self.__diffUpdatesFiles: for oldFileName, newFileName, lineNumber in fileSeparators: if oldFileName == newFileName: @@ -2585,21 +2761,21 @@ for fileName in (oldFileName, newFileName): if fileName != "__NULL__": items = self.filesTree.findItems( - fileName, Qt.MatchFlag.MatchExactly, 1) + fileName, Qt.MatchFlag.MatchExactly, 1 + ) for item in items: - item.setData(0, self.__diffFileLineRole, - lineNumber) - + item.setData(0, self.__diffFileLineRole, lineNumber) + tc = self.diffEdit.textCursor() tc.movePosition(QTextCursor.MoveOperation.Start) self.diffEdit.setTextCursor(tc) self.diffEdit.ensureCursorVisible() - + @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) def on_filesTree_currentItemChanged(self, current, previous): """ Private slot called, when the current item of the files tree changes. - + @param current reference to the new current item (QTreeWidgetItem) @param previous reference to the old current item (QTreeWidgetItem) """ @@ -2622,20 +2798,23 @@ tc.movePosition(QTextCursor.MoveOperation.End) self.diffEdit.setTextCursor(tc) self.diffEdit.ensureCursorVisible() - + # step 2: move cursor to desired line tc = self.diffEdit.textCursor() delta = tc.blockNumber() - para - tc.movePosition(QTextCursor.MoveOperation.PreviousBlock, - QTextCursor.MoveMode.MoveAnchor, delta) + tc.movePosition( + QTextCursor.MoveOperation.PreviousBlock, + QTextCursor.MoveMode.MoveAnchor, + delta, + ) self.diffEdit.setTextCursor(tc) self.diffEdit.ensureCursorVisible() - + @pyqtSlot(str) def on_diffSelectLabel_linkActivated(self, link): """ Private slot to handle the selection of a diff target. - + @param link activated link @type str """ @@ -2645,47 +2824,49 @@ with contextlib.suppress(ValueError): parent = int(parent) self.__generateDiffs(parent) - + @pyqtSlot(str) def on_saveLabel_linkActivated(self, link): """ Private slot to handle the selection of the save link. - + @param link activated link @type str """ if ":" not in link: return - + scheme, rest = link.split(":", 1) if scheme != "save" or rest != "me": return - + if self.projectMode: if self.__filename is None: - fname = "{0}.diff".format(os.path.splitext( - ericApp().getObject("Project").getProjectFile())[0]) + fname = "{0}.diff".format( + os.path.splitext(ericApp().getObject("Project").getProjectFile())[0] + ) else: fname = self.vcs.splitPath(self.__filename)[0] fname += "/{0}.diff".format(os.path.split(fname)[-1]) else: dname, fname = self.vcs.splitPath(self.__filename) - if fname != '.': + if fname != ".": fname = "{0}.diff".format(self.__filename) else: fname = dname - + fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( self, self.tr("Save Diff"), fname, self.tr("Patch Files (*.diff)"), None, - EricFileDialog.DontConfirmOverwrite) - + EricFileDialog.DontConfirmOverwrite, + ) + if not fname: return # user aborted - + fpath = pathlib.Path(fname) if not fpath.suffix: ex = selectedFilter.split("(*")[1].split(")")[0] @@ -2695,29 +2876,33 @@ res = EricMessageBox.yesNo( self, self.tr("Save Diff"), - self.tr("<p>The patch file <b>{0}</b> already exists." - " Overwrite it?</p>").format(fpath), - icon=EricMessageBox.Warning) + self.tr( + "<p>The patch file <b>{0}</b> already exists." " Overwrite it?</p>" + ).format(fpath), + icon=EricMessageBox.Warning, + ) if not res: return - + eol = ericApp().getObject("Project").getEolString() try: with fpath.open("w", encoding="utf-8", newline="") as f: f.write(eol.join(self.diffEdit.toPlainText().splitlines())) except OSError as why: EricMessageBox.critical( - self, self.tr('Save Diff'), + self, + self.tr("Save Diff"), self.tr( - '<p>The patch file <b>{0}</b> could not be saved.' - '<br>Reason: {1}</p>') - .format(fpath, str(why))) - + "<p>The patch file <b>{0}</b> could not be saved." + "<br>Reason: {1}</p>" + ).format(fpath, str(why)), + ) + @pyqtSlot(str) def on_sbsSelectLabel_linkActivated(self, link): """ Private slot to handle selection of a side-by-side link. - + @param link text of the selected link @type str """