--- a/src/eric7/Plugins/VcsPlugins/vcsGit/GitLogBrowserDialog.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/Plugins/VcsPlugins/vcsGit/GitLogBrowserDialog.py Wed Jul 13 14:55:47 2022 +0200 @@ -13,15 +13,17 @@ import contextlib import pathlib -from PyQt6.QtCore import ( - pyqtSlot, Qt, QDate, QProcess, QTimer, QSize, QPoint -) -from PyQt6.QtGui import ( - QColor, QPixmap, QPainter, QPen, QIcon, QTextCursor, QPalette -) +from PyQt6.QtCore import pyqtSlot, Qt, QDate, QProcess, QTimer, QSize, QPoint +from PyQt6.QtGui import QColor, QPixmap, QPainter, QPen, 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 @@ -38,21 +40,47 @@ import UI.PixmapCache import Preferences -COLORNAMES = ["red", "green", "purple", "cyan", "olive", "magenta", - "gray", "yellow", "darkred", "darkgreen", "darkblue", - "darkcyan", "darkmagenta", "blue"] +COLORNAMES = [ + "red", + "green", + "purple", + "cyan", + "olive", + "magenta", + "gray", + "yellow", + "darkred", + "darkgreen", + "darkblue", + "darkcyan", + "darkmagenta", + "blue", +] 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 GitLogBrowserDialog(QWidget, Ui_GitLogBrowserDialog): """ Class implementing a dialog to browse the log history. """ + IconColumn = 0 CommitIdColumn = 1 AuthorColumn = 2 @@ -62,73 +90,70 @@ SubjectColumn = 6 BranchColumn = 7 TagsColumn = 8 - + def __init__(self, vcs, parent=None): """ Constructor - + @param vcs reference to the vcs object @param parent parent widget (QWidget) """ 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) - - 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( - 1, Qt.SortOrder.AscendingOrder) - + self.filesTree.header().setSortIndicator(1, 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.refreshButton.setToolTip( - self.tr("Press to refresh the list of commits")) + self.tr("&Refresh"), QDialogButtonBox.ButtonRole.ActionRole + ) + self.refreshButton.setToolTip(self.tr("Press to refresh the list of commits")) 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("Commit ID"), "commitId") self.fieldCombo.addItem(self.tr("Author"), "author") self.fieldCombo.addItem(self.tr("Committer"), "committer") self.fieldCombo.addItem(self.tr("Subject"), "subject") self.fieldCombo.addItem(self.tr("File"), "file") - + self.__logTreeNormalFont = self.logTree.font() self.__logTreeNormalFont.setBold(False) self.__logTreeBoldFont = self.logTree.font() self.__logTreeBoldFont.setBold(True) self.__logTreeHasDarkBackground = ericApp().usesDarkPalette() - + font = Preferences.getEditorOtherFonts("MonospacedFont") self.diffEdit.document().setDefaultFont(font) - + self.diffHighlighter = GitDiffHighlighter(self.diffEdit.document()) self.__diffGenerator = GitDiffGenerator(vcs, self) self.__diffGenerator.finished.connect(self.__generatorFinished) - + self.vcs = vcs - + self.__detailsTemplate = self.tr( "<table>" "<tr><td><b>Commit ID</b></td><td>{0}</td></tr>" @@ -141,50 +166,44 @@ "{9}" "</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.__branchesTemplate = self.tr( "<tr><td><b>Branches</b></td><td>{0}</td></tr>" ) - self.__tagsTemplate = self.tr( - "<tr><td><b>Tags</b></td><td>{0}</td></tr>" - ) - self.__mesageTemplate = self.tr( - "<tr><td><b>Message</b></td><td>{0}</td></tr>" - ) - + self.__tagsTemplate = self.tr("<tr><td><b>Tags</b></td><td>{0}</td></tr>") + self.__mesageTemplate = self.tr("<tr><td><b>Message</b></td><td>{0}</td></tr>") + self.__formatTemplate = ( - 'format:recordstart%n' - 'commit|%h%n' - 'parents|%p%n' - 'author|%an%n' - 'authormail|%ae%n' - 'authordate|%ai%n' - 'committer|%cn%n' - 'committermail|%ce%n' - 'committerdate|%ci%n' - 'refnames|%d%n' - 'subject|%s%n' - 'bodystart%n' - '%b%n' - 'bodyend%n' + "format:recordstart%n" + "commit|%h%n" + "parents|%p%n" + "author|%an%n" + "authormail|%ae%n" + "authordate|%ai%n" + "committer|%cn%n" + "committermail|%ce%n" + "committerdate|%ci%n" + "refnames|%d%n" + "subject|%s%n" + "bodystart%n" + "%b%n" + "bodyend%n" ) - + self.__filename = "" self.__isFile = False self.__selectedCommitIDs = [] self.intercept = False - + self.__initData() - + self.fromDate.setDisplayFormat("yyyy-MM-dd") self.toDate.setDisplayFormat("yyyy-MM-dd") self.__resetUI() - + # roles used in the log tree self.__subjectRole = Qt.ItemDataRole.UserRole self.__messageRole = Qt.ItemDataRole.UserRole + 1 @@ -194,120 +213,107 @@ self.__branchesRole = Qt.ItemDataRole.UserRole + 5 self.__authorMailRole = Qt.ItemDataRole.UserRole + 6 self.__committerMailRole = Qt.ItemDataRole.UserRole + 7 - + # roles used in the file tree self.__diffFileLineRole = Qt.ItemDataRole.UserRole - + self.__process = EricOverrideCursorProcess() self.__process.finished.connect(self.__procFinished) self.__process.readyReadStandardOutput.connect(self.__readStdout) self.__process.readyReadStandardError.connect(self.__readStderr) - + self.flags = { - 'A': self.tr('Added'), - 'D': self.tr('Deleted'), - 'M': self.tr('Modified'), - 'C': self.tr('Copied'), - 'R': self.tr('Renamed'), - 'T': self.tr('Type changed'), - 'U': self.tr('Unmerged'), - 'X': self.tr('Unknown'), + "A": self.tr("Added"), + "D": self.tr("Deleted"), + "M": self.tr("Modified"), + "C": self.tr("Copied"), + "R": self.tr("Renamed"), + "T": self.tr("Type changed"), + "U": self.tr("Unmerged"), + "X": self.tr("Unknown"), } - + 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.detailsEdit.anchorClicked.connect(self.__commitIdClicked) - + self.__initLogTreeContextMenu() self.__initActionsMenu() - + self.__finishCallbacks = [] - + 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 __initLogTreeContextMenu(self): """ Private method to initialize the log tree context menu. """ self.__logTreeMenu = QMenu() - + # commit ID column - act = self.__logTreeMenu.addAction( - self.tr("Show Commit ID Column")) - act.setToolTip(self.tr( - "Press to show the commit ID column")) + act = self.__logTreeMenu.addAction(self.tr("Show Commit ID Column")) + act.setToolTip(self.tr("Press to show the commit ID column")) act.setCheckable(True) - act.setChecked(self.vcs.getPlugin().getPreferences( - "ShowCommitIdColumn")) + act.setChecked(self.vcs.getPlugin().getPreferences("ShowCommitIdColumn")) act.triggered.connect(self.__showCommitIdColumn) - + # author and date columns - act = self.__logTreeMenu.addAction( - self.tr("Show Author Columns")) - act.setToolTip(self.tr( - "Press to show the author columns")) + act = self.__logTreeMenu.addAction(self.tr("Show Author Columns")) + act.setToolTip(self.tr("Press to show the author columns")) act.setCheckable(True) - act.setChecked(self.vcs.getPlugin().getPreferences( - "ShowAuthorColumns")) + act.setChecked(self.vcs.getPlugin().getPreferences("ShowAuthorColumns")) act.triggered.connect(self.__showAuthorColumns) - + # committer and commit date columns - act = self.__logTreeMenu.addAction( - self.tr("Show Committer Columns")) - act.setToolTip(self.tr( - "Press to show the committer columns")) + act = self.__logTreeMenu.addAction(self.tr("Show Committer Columns")) + act.setToolTip(self.tr("Press to show the committer columns")) act.setCheckable(True) - act.setChecked(self.vcs.getPlugin().getPreferences( - "ShowCommitterColumns")) + act.setChecked(self.vcs.getPlugin().getPreferences("ShowCommitterColumns")) act.triggered.connect(self.__showCommitterColumns) - + # branches column - act = self.__logTreeMenu.addAction( - self.tr("Show Branches Column")) - act.setToolTip(self.tr( - "Press to show the branches column")) + act = self.__logTreeMenu.addAction(self.tr("Show Branches Column")) + act.setToolTip(self.tr("Press to show the branches column")) act.setCheckable(True) - act.setChecked(self.vcs.getPlugin().getPreferences( - "ShowBranchesColumn")) + act.setChecked(self.vcs.getPlugin().getPreferences("ShowBranchesColumn")) act.triggered.connect(self.__showBranchesColumn) - + # tags column - act = self.__logTreeMenu.addAction( - self.tr("Show Tags Column")) - act.setToolTip(self.tr( - "Press to show the Tags column")) + act = self.__logTreeMenu.addAction(self.tr("Show Tags Column")) + act.setToolTip(self.tr("Press to show the Tags column")) act.setCheckable(True) - act.setChecked(self.vcs.getPlugin().getPreferences( - "ShowTagsColumn")) + act.setChecked(self.vcs.getPlugin().getPreferences("ShowTagsColumn")) act.triggered.connect(self.__showTagsColumn) - + # set column visibility as configured - self.__showCommitIdColumn(self.vcs.getPlugin().getPreferences( - "ShowCommitIdColumn")) - self.__showAuthorColumns(self.vcs.getPlugin().getPreferences( - "ShowAuthorColumns")) - self.__showCommitterColumns(self.vcs.getPlugin().getPreferences( - "ShowCommitterColumns")) - self.__showBranchesColumn(self.vcs.getPlugin().getPreferences( - "ShowBranchesColumn")) - self.__showTagsColumn(self.vcs.getPlugin().getPreferences( - "ShowTagsColumn")) - + self.__showCommitIdColumn( + self.vcs.getPlugin().getPreferences("ShowCommitIdColumn") + ) + self.__showAuthorColumns( + self.vcs.getPlugin().getPreferences("ShowAuthorColumns") + ) + self.__showCommitterColumns( + self.vcs.getPlugin().getPreferences("ShowCommitterColumns") + ) + self.__showBranchesColumn( + self.vcs.getPlugin().getPreferences("ShowBranchesColumn") + ) + self.__showTagsColumn(self.vcs.getPlugin().getPreferences("ShowTagsColumn")) + def __initActionsMenu(self): """ Private method to initialize the actions menu. @@ -315,48 +321,62 @@ self.__actionsMenu = QMenu() self.__actionsMenu.setTearOffEnabled(True) self.__actionsMenu.setToolTipsVisible(True) - + self.__cherryAct = self.__actionsMenu.addAction( - self.tr("Copy Commits"), self.__cherryActTriggered) - self.__cherryAct.setToolTip(self.tr( - "Cherry-pick the selected commits to the current branch")) - + self.tr("Copy Commits"), self.__cherryActTriggered + ) + self.__cherryAct.setToolTip( + self.tr("Cherry-pick the selected commits to the current branch") + ) + self.__actionsMenu.addSeparator() - + self.__tagAct = self.__actionsMenu.addAction( - self.tr("Tag"), self.__tagActTriggered) + self.tr("Tag"), self.__tagActTriggered + ) self.__tagAct.setToolTip(self.tr("Tag the selected commit")) - + self.__branchAct = self.__actionsMenu.addAction( - self.tr("Branch"), self.__branchActTriggered) - self.__branchAct.setToolTip(self.tr( - "Create a new branch at the selected commit.")) + self.tr("Branch"), self.__branchActTriggered + ) + self.__branchAct.setToolTip( + self.tr("Create a new branch at the selected commit.") + ) self.__branchSwitchAct = self.__actionsMenu.addAction( - self.tr("Branch && Switch"), self.__branchSwitchActTriggered) - self.__branchSwitchAct.setToolTip(self.tr( - "Create a new branch at the selected commit and switch" - " the work tree to it.")) - + self.tr("Branch && Switch"), self.__branchSwitchActTriggered + ) + self.__branchSwitchAct.setToolTip( + self.tr( + "Create a new branch at the selected commit and switch" + " the work tree to it." + ) + ) + self.__switchAct = self.__actionsMenu.addAction( - self.tr("Switch"), self.__switchActTriggered) - self.__switchAct.setToolTip(self.tr( - "Switch the working directory to the selected commit")) + self.tr("Switch"), self.__switchActTriggered + ) + self.__switchAct.setToolTip( + self.tr("Switch the working directory to the selected commit") + ) self.__actionsMenu.addSeparator() - + self.__shortlogAct = self.__actionsMenu.addAction( - self.tr("Show Short Log"), self.__shortlogActTriggered) - self.__shortlogAct.setToolTip(self.tr( - "Show a dialog with a log output for release notes")) - + self.tr("Show Short Log"), self.__shortlogActTriggered + ) + self.__shortlogAct.setToolTip( + self.tr("Show a dialog with a log output for release notes") + ) + self.__describeAct = self.__actionsMenu.addAction( - self.tr("Describe"), self.__describeActTriggered) - self.__describeAct.setToolTip(self.tr( - "Show the most recent tag reachable from a commit")) - - self.actionsButton.setIcon( - UI.PixmapCache.getIcon("actionsToolButton")) + self.tr("Describe"), self.__describeActTriggered + ) + self.__describeAct.setToolTip( + self.tr("Show the most recent tag reachable from a commit") + ) + + self.actionsButton.setIcon(UI.PixmapCache.getIcon("actionsToolButton")) self.actionsButton.setMenu(self.__actionsMenu) - + def __initData(self): """ Private method to (re-)initialize some data. @@ -364,48 +384,48 @@ 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.__skipEntries = 0 self.projectMode = False - + # attributes to store log graph data self.__commitIds = [] self.__commitColors = {} self.__commitColor = 0 - + self.__projectRevision = "" - + self.__childrenInfo = collections.defaultdict(list) - + def closeEvent(self, e): """ Protected slot implementing a close event handler. - + @param e close event (QCloseEvent) """ if ( - self.__process is not None and - self.__process.state() != QProcess.ProcessState.NotRunning + self.__process is not None + and self.__process.state() != QProcess.ProcessState.NotRunning ): self.__process.terminate() QTimer.singleShot(2000, self.__process.kill) self.__process.waitForFinished(3000) - + + 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. @@ -413,9 +433,9 @@ self.__reloadGeometry() self.__restoreSplitterStates() self.__resetUI() - + super().show() - + def __reloadGeometry(self): """ Private method to restore the geometry. @@ -426,19 +446,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. @@ -446,29 +465,27 @@ self.fromDate.setDate(QDate.currentDate()) self.toDate.setDate(QDate.currentDate()) self.fieldCombo.setCurrentIndex(self.fieldCombo.findData("subject")) - 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") + ) + self.logTree.clear() - + 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. @@ -476,11 +493,11 @@ self.filesTree.setSortingEnabled(True) self.filesTree.sortItems(1, Qt.SortOrder.AscendingOrder) self.filesTree.setSortingEnabled(False) - + 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 @@ -490,11 +507,11 @@ return LIGHTCOLORS[n % len(LIGHTCOLORS)] else: return COLORS[n % len(COLORS)] - + def __generateEdges(self, commitId, parents): """ Private method to generate edge info for the give data. - + @param commitId commit id to calculate edge info for (string) @param parents list of parent commits (list of strings) @return tuple containing the column and color index for @@ -507,15 +524,15 @@ self.__commitIds.append(commitId) self.__commitColors[commitId] = self.__commitColor self.__commitColor += 1 - + col = self.__commitIds.index(commitId) color = self.__commitColors.pop(commitId) nextCommitIds = self.__commitIds[:] - + # add parents to next addparents = [p for p in parents if p not in nextCommitIds] - nextCommitIds[col:col + 1] = addparents - + nextCommitIds[col : col + 1] = addparents + # set colors for the parents for i, p in enumerate(addparents): if not i: @@ -523,30 +540,35 @@ else: self.__commitColors[p] = self.__commitColor self.__commitColor += 1 - + # add edges to the graph edges = [] if parents: for ecol, ecommitId in enumerate(self.__commitIds): if ecommitId in nextCommitIds: edges.append( - (ecol, nextCommitIds.index(ecommitId), - self.__commitColors[ecommitId])) + ( + ecol, + nextCommitIds.index(ecommitId), + self.__commitColors[ecommitId], + ) + ) elif ecommitId == commitId: for p in parents: edges.append( - (ecol, nextCommitIds.index(p), - self.__commitColors[p])) - + (ecol, nextCommitIds.index(p), self.__commitColors[p]) + ) + self.__commitIds = nextCommitIds return col, color, edges - - def __generateIcon(self, column, color, bottomedges, topedges, dotColor, - currentCommit): + + def __generateIcon( + self, column, color, bottomedges, topedges, dotColor, currentCommit + ): """ Private method to generate an icon containing the revision tree for the given data. - + @param column column index of the revision (integer) @param color color of the node (integer) @param bottomedges list of edges for the bottom of the node @@ -558,30 +580,30 @@ current commit (boolean) @return icon for the node (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 - + 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))) @@ -590,12 +612,12 @@ x1 = col2x(start, radius) x2 = col2x(end, radius) painter.drawLine(x1, dot_y + y1, x2, dot_y + y2) - + penradius = 1 pencolor = self.logTree.palette().color(QPalette.ColorRole.Text) - + dot_y = (h // 2) - radius // 2 - + # draw a dot for the revision if currentCommit: # enlarge dot for the current revision @@ -610,53 +632,64 @@ painter.drawEllipse(dot_x, dot_y, radius, radius) painter.end() return QIcon(pix) - + def __identifyProject(self): """ Private method to determine the revision of the project directory. """ errMsg = "" - + args = self.vcs.initCommand("show") - args.append("--abbrev={0}".format( - self.vcs.getPlugin().getPreferences("CommitIdLength"))) + args.append( + "--abbrev={0}".format(self.vcs.getPlugin().getPreferences("CommitIdLength")) + ) args.append("--format=%h") args.append("--no-patch") args.append("HEAD") - + output = "" process = QProcess() process.setWorkingDirectory(self.repodir) - process.start('git', args) + process.start("git", args) procStarted = process.waitForStarted(5000) if procStarted: finished = process.waitForFinished(30000) if finished and process.exitCode() == 0: - output = str(process.readAllStandardOutput(), - Preferences.getSystem("IOEncoding"), - 'replace') + output = str( + process.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + "replace", + ) else: if not finished: - errMsg = self.tr( - "The git process did not finish within 30s.") + errMsg = self.tr("The git process did not finish within 30s.") else: errMsg = self.tr("Could not start the git executable.") - + if errMsg: - EricMessageBox.critical( - self, - self.tr("Git Error"), - errMsg) - + EricMessageBox.critical(self, self.tr("Git Error"), errMsg) + if output: self.__projectRevision = output.strip() - - def __generateLogItem(self, author, date, committer, commitDate, subject, - message, commitId, changedPaths, parents, refnames, - authorMail, committerMail): + + def __generateLogItem( + self, + author, + date, + committer, + commitDate, + subject, + message, + commitId, + changedPaths, + parents, + refnames, + authorMail, + committerMail, + ): """ Private method to generate a log tree entry. - + @param author author info (string) @param date date info (string) @param committer committer info (string) @@ -692,9 +725,10 @@ else: branches.append(name) allBranches.append(name) - + logMessageColumnWidth = self.vcs.getPlugin().getPreferences( - "LogSubjectColumnWidth") + "LogSubjectColumnWidth" + ) msgtxt = subject if logMessageColumnWidth and len(msgtxt) > logMessageColumnWidth: msgtxt = "{0}...".format(msgtxt[:logMessageColumnWidth]) @@ -710,10 +744,10 @@ ", ".join(tags), ] itm = QTreeWidgetItem(self.logTree, columnLabels) - + parents = [p.strip() for p in parents.split()] column, color, edges = self.__generateEdges(commitId, parents) - + itm.setData(0, self.__subjectRole, subject) itm.setData(0, self.__messageRole, message) itm.setData(0, self.__changesRole, changedPaths) @@ -727,26 +761,31 @@ itm.setData(0, self.__parentsRole, parents) for parent in parents: self.__childrenInfo[parent].append(commitId) - + 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("blue"), - commitId == self.__projectRevision) + + icon = self.__generateIcon( + column, + color, + edges, + topedges, + QColor("blue"), + commitId == self.__projectRevision, + ) itm.setIcon(0, icon) - + return itm - + def __generateFileItem(self, action, path, copyfrom, additions, deletions): """ Private method to generate a changed files tree entry. - + @param action indicator for the change action ("A", "C", "D", "M", "R", "T", "U", "X") @param path path of the file in the repository (string) @@ -759,174 +798,177 @@ # includes confidence level confidence = int(action[1:]) actionTxt = self.tr("{0} ({1}%)", "action, confidence").format( - self.flags[action[0]], confidence) + self.flags[action[0]], confidence + ) else: actionTxt = self.flags[action] - itm = QTreeWidgetItem(self.filesTree, [ - actionTxt, - path, - str(additions), - str(deletions), - copyfrom, - ]) - + itm = QTreeWidgetItem( + self.filesTree, + [ + actionTxt, + path, + str(additions), + str(deletions), + copyfrom, + ], + ) + itm.setTextAlignment(2, Qt.AlignmentFlag.AlignRight) itm.setTextAlignment(3, Qt.AlignmentFlag.AlignRight) - + return itm - + def __getLogEntries(self, skip=0, noEntries=0): """ Private method to retrieve log entries from the repository. - + @param skip number of log entries to skip (integer) @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() - + self.buf = [] self.cancelled = False self.errors.clear() self.intercept = False - + if noEntries == 0: noEntries = self.limitSpinBox.value() - + args = self.vcs.initCommand("log") - args.append('--max-count={0}'.format(noEntries)) - args.append('--numstat') - args.append('--abbrev={0}'.format( - self.vcs.getPlugin().getPreferences("CommitIdLength"))) + args.append("--max-count={0}".format(noEntries)) + args.append("--numstat") + args.append( + "--abbrev={0}".format(self.vcs.getPlugin().getPreferences("CommitIdLength")) + ) if self.vcs.getPlugin().getPreferences("FindCopiesHarder"): - args.append('--find-copies-harder') - args.append('--format={0}'.format(self.__formatTemplate)) - args.append('--full-history') - args.append('--all') - args.append('--skip={0}'.format(skip)) + args.append("--find-copies-harder") + args.append("--format={0}".format(self.__formatTemplate)) + args.append("--full-history") + args.append("--all") + args.append("--skip={0}".format(skip)) if not self.projectMode: if not self.stopCheckBox.isChecked(): - args.append('--follow') - args.append('--') + args.append("--follow") + args.append("--") args.append(self.__filename) - + self.__process.kill() - + self.__process.setWorkingDirectory(self.repodir) - - self.__process.start('git', args) + + self.__process.start("git", args) procStarted = self.__process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() EricMessageBox.critical( self, - self.tr('Process Generation Error'), + self.tr("Process Generation Error"), self.tr( - 'The process {0} could not be started. ' - 'Ensure, that it is in the search path.' - ).format('git')) - + "The process {0} could not be started. " + "Ensure, that it is in the search path." + ).format("git"), + ) + def start(self, fn, isFile=False, noEntries=0): """ Public slot to start the git log command. - + @param fn filename to show the log for (string) @param isFile flag indicating log for a file is to be shown (boolean) @param noEntries number of entries to get (0 = default) (int) """ self.__isFile = isFile - + self.sbsSelectLabel.clear() - + self.errorGroup.hide() QApplication.processEvents() - + self.__initData() - + self.__filename = fn self.dname, self.fname = self.vcs.splitPath(fn) - + # find the root of the repo self.repodir = self.dname while not os.path.isdir(os.path.join(self.repodir, self.vcs.adminDir)): self.repodir = os.path.dirname(self.repodir) if os.path.splitdrive(self.repodir)[1] == os.sep: return - - self.projectMode = (self.fname == "." and self.dname == self.repodir) + + self.projectMode = self.fname == "." and self.dname == self.repodir self.stopCheckBox.setDisabled(self.projectMode or self.fname == ".") self.activateWindow() self.raise_() - + self.logTree.clear() self.__started = True self.__identifyProject() self.__getLogEntries(noEntries=noEntries) - + def __procFinished(self, exitCode, exitStatus): """ Private slot connected to the finished signal. - + @param exitCode exit code of the process (integer) @param exitStatus exit status of the process (QProcess.ExitStatus) """ self.__processBuffer() self.__finish() - + def __finish(self): """ Private slot called when the process finished or the user pressed the button. """ if ( - self.__process is not None and - self.__process.state() != QProcess.ProcessState.NotRunning + self.__process is not None + and self.__process.state() != QProcess.ProcessState.NotRunning ): self.__process.terminate() QTimer.singleShot(2000, self.__process.kill) self.__process.waitForFinished(3000) - - 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.inputGroup.setEnabled(False) self.inputGroup.hide() self.refreshButton.setEnabled(True) - + while self.__finishCallbacks: self.__finishCallbacks.pop(0)() - + def __processBufferItem(self, logEntry): """ Private method to process a log entry. - + @param logEntry dictionary as generated by __processBuffer """ self.__generateLogItem( - logEntry["author"], logEntry["authordate"], - logEntry["committer"], logEntry["committerdate"], - logEntry["subject"], logEntry["body"], - logEntry["commit"], logEntry["changed_files"], - logEntry["parents"], logEntry["refnames"], - logEntry["authormail"], logEntry["committermail"] + logEntry["author"], + logEntry["authordate"], + logEntry["committer"], + logEntry["committerdate"], + logEntry["subject"], + logEntry["body"], + logEntry["commit"], + logEntry["changed_files"], + logEntry["parents"], + logEntry["refnames"], + logEntry["authormail"], + logEntry["committermail"], ) for date in [logEntry["authordate"], logEntry["committerdate"]]: dt = QDate.fromString(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: @@ -934,7 +976,7 @@ self.__maxDate = dt if self.__minDate > dt: self.__minDate = dt - + def __processBuffer(self): """ Private method to process the buffered output of the git log command. @@ -942,7 +984,7 @@ noEntries = 0 logEntry = {"changed_files": []} descriptionBody = False - + for line in self.buf: line = line.rstrip() if line == "recordstart": @@ -980,46 +1022,63 @@ dst = head + middleDst.strip() + tail else: src, dst = changeInfo[2].split("=>") - logEntry["changed_files"].append({ - "action": "C", - "added": changeInfo[0].strip(), - "deleted": changeInfo[1].strip(), - "path": dst.strip(), - "copyfrom": src.strip(), - }) + logEntry["changed_files"].append( + { + "action": "C", + "added": changeInfo[0].strip(), + "deleted": changeInfo[1].strip(), + "path": dst.strip(), + "copyfrom": src.strip(), + } + ) else: - logEntry["changed_files"].append({ - "action": "M", - "added": changeInfo[0].strip(), - "deleted": changeInfo[1].strip(), - "path": changeInfo[2].strip(), - "copyfrom": "", - }) + logEntry["changed_files"].append( + { + "action": "M", + "added": changeInfo[0].strip(), + "deleted": changeInfo[1].strip(), + "path": changeInfo[2].strip(), + "copyfrom": "", + } + ) else: try: key, value = line.split("|", 1) except ValueError: key = "" value = line - if key in ("commit", "parents", "author", "authormail", - "authordate", "committer", "committermail", - "committerdate", "refnames", "subject"): + if key in ( + "commit", + "parents", + "author", + "authormail", + "authordate", + "committer", + "committermail", + "committerdate", + "refnames", + "subject", + ): logEntry[key] = value.strip() if len(logEntry) > 1: self.__processBufferItem(logEntry) noEntries += 1 - + self.__resizeColumnsLog() - + if self.__started: if self.__selectedCommitIDs: - self.logTree.setCurrentItem(self.logTree.findItems( - self.__selectedCommitIDs[0], Qt.MatchFlag.MatchExactly, - self.CommitIdColumn)[0]) + self.logTree.setCurrentItem( + self.logTree.findItems( + self.__selectedCommitIDs[0], + Qt.MatchFlag.MatchExactly, + self.CommitIdColumn, + )[0] + ) else: self.logTree.setCurrentItem(self.logTree.topLevelItem(0)) self.__started = False - + self.__skipEntries += noEntries if noEntries < self.limitSpinBox.value() and not self.cancelled: self.nextButton.setEnabled(False) @@ -1027,7 +1086,7 @@ else: self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True) - + # update the log filters self.__filterLogsEnabled = False self.fromDate.setMinimumDate(self.__minDate) @@ -1036,114 +1095,115 @@ self.toDate.setMinimumDate(self.__minDate) self.toDate.setMaximumDate(self.__maxDate) self.toDate.setDate(self.__maxDate) - + self.__filterLogsEnabled = True if self.__actionMode() == "filter": self.__filterLogs() - + self.__updateToolMenuActions() - + # restore selected items if self.__selectedCommitIDs: for commitID in self.__selectedCommitIDs: items = self.logTree.findItems( - commitID, Qt.MatchFlag.MatchExactly, self.CommitIdColumn) + commitID, Qt.MatchFlag.MatchExactly, self.CommitIdColumn + ) if items: items[0].setSelected(True) self.__selectedCommitIDs = [] - + def __readStdout(self): """ Private slot to handle the readyReadStandardOutput signal. - + It reads the output of the process and inserts it into a buffer. """ self.__process.setReadChannel(QProcess.ProcessChannel.StandardOutput) - + while self.__process.canReadLine(): - line = str(self.__process.readLine(), - Preferences.getSystem("IOEncoding"), - 'replace') + line = str( + self.__process.readLine(), + Preferences.getSystem("IOEncoding"), + "replace", + ) self.buf.append(line) - + def __readStderr(self): """ Private slot to handle the readyReadStandardError signal. - + It reads the error output of the process and inserts it into the error pane. """ if self.__process is not None: - s = str(self.__process.readAllStandardError(), - Preferences.getSystem("IOEncoding"), - 'replace') + s = str( + self.__process.readAllStandardError(), + Preferences.getSystem("IOEncoding"), + "replace", + ) self.__showError(s) - + 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() - + # show input in case the process asked for some input self.inputGroup.setEnabled(True) self.inputGroup.show() - + 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.__finish() elif button == self.refreshButton: self.on_refreshButton_clicked() - + @pyqtSlot() def on_refreshButton_clicked(self): """ Private slot to refresh the log. """ - 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.__selectedCommitIDs = [] for item in self.logTree.selectedItems(): self.__selectedCommitIDs.append(item.text(self.CommitIdColumn)) - - self.start(self.__filename, isFile=self.__isFile, - noEntries=self.logTree.topLevelItemCount()) - + + self.start( + self.__filename, + isFile=self.__isFile, + noEntries=self.logTree.topLevelItemCount(), + ) + def on_passwordCheckBox_toggled(self, isOn): """ Private slot to handle the password checkbox toggled. - + @param isOn flag indicating the status of the check box (boolean) """ if isOn: self.input.setEchoMode(QLineEdit.EchoMode.Password) else: self.input.setEchoMode(QLineEdit.EchoMode.Normal) - + @pyqtSlot() def on_sendButton_clicked(self): """ @@ -1151,7 +1211,7 @@ """ inputTxt = self.input.text() inputTxt += os.linesep - + if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() @@ -1159,23 +1219,23 @@ self.errors.insertPlainText(inputTxt) self.errors.ensureCursorVisible() self.errorGroup.show() - + self.__process.write(strToQByteArray(inputTxt)) - + self.passwordCheckBox.setChecked(False) self.input.clear() - + def on_input_returnPressed(self): """ Private slot to handle the press of the return key in the input field. """ self.intercept = True self.on_sendButton_clicked() - + def keyPressEvent(self, evt): """ Protected slot to handle a key press event. - + @param evt the key press event (QKeyEvent) """ if self.intercept: @@ -1183,11 +1243,11 @@ evt.accept() return super().keyPressEvent(evt) - + 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) """ @@ -1203,8 +1263,7 @@ fieldIndex = self.CommitIdColumn 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": @@ -1215,9 +1274,9 @@ fieldIndex = self.__subjectRole searchRx = re.compile(self.rxEdit.text(), re.IGNORECASE) indexIsRole = True - + return fieldIndex, searchRx, indexIsRole - + def __filterLogs(self): """ Private method to filter the log entries. @@ -1226,7 +1285,7 @@ from_ = self.fromDate.date().toString("yyyy-MM-dd") to_ = self.toDate.date().addDays(1).toString("yyyy-MM-dd") fieldIndex, searchRx, indexIsRole = self.__prepareFieldSearch() - + visibleItemCount = self.logTree.topLevelItemCount() currentItem = self.logTree.currentItem() for topIndex in range(self.logTree.topLevelItemCount()): @@ -1235,8 +1294,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: # Filter based on complete subject text @@ -1244,9 +1303,9 @@ else: txt = topItem.text(fieldIndex) if ( - topItem.text(self.DateColumn) <= to_ and - topItem.text(self.DateColumn) >= from_ and - searchRx.search(txt) is not None + topItem.text(self.DateColumn) <= to_ + and topItem.text(self.DateColumn) >= from_ + and searchRx.search(txt) is not None ): topItem.setHidden(False) if topItem is currentItem: @@ -1257,9 +1316,9 @@ self.filesTree.clear() visibleItemCount -= 1 self.logTree.header().setSectionHidden( - self.IconColumn, - visibleItemCount != self.logTree.topLevelItemCount()) - + self.IconColumn, visibleItemCount != self.logTree.topLevelItemCount() + ) + def __updateSbsSelectLabel(self): """ Private slot to update the enabled status of the diff buttons. @@ -1275,24 +1334,30 @@ parentLinks = [] for index in range(len(parents)): parentLinks.append( - '<a href="sbsdiff:{0}_{1}"> {2} </a>' - .format(parents[index], commit2, index + 1)) + '<a href="sbsdiff:{0}_{1}"> {2} </a>'.format( + parents[index], commit2, 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: commit2 = selectedItems[0].text(self.CommitIdColumn) commit1 = selectedItems[1].text(self.CommitIdColumn) index2 = self.logTree.indexOfTopLevelItem(selectedItems[0]) index1 = self.logTree.indexOfTopLevelItem(selectedItems[1]) - + if index2 < index1: # swap to always compare old to new commit1, commit2 = commit2, commit1 - self.sbsSelectLabel.setText(self.tr( - '<a href="sbsdiff:{0}_{1}">Side-by-Side Compare</a>') - .format(commit1, commit2)) - + self.sbsSelectLabel.setText( + self.tr( + '<a href="sbsdiff:{0}_{1}">Side-by-Side Compare</a>' + ).format(commit1, commit2) + ) + def __updateToolMenuActions(self): """ Private slot to update the status of the tool menu actions and @@ -1307,11 +1372,11 @@ self.__branchAct.setEnabled(selectCount == 1) self.__branchSwitchAct.setEnabled(selectCount == 1) self.__shortlogAct.setEnabled(selectCount == 1) - + self.actionsButton.setEnabled(True) else: self.actionsButton.setEnabled(False) - + def __updateDetailsAndFiles(self): """ Private slot to update the details and file changes panes. @@ -1319,11 +1384,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() @@ -1337,7 +1401,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]), @@ -1345,12 +1410,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 @@ -1358,54 +1423,51 @@ """ if itm is not None: commitId = itm.text(self.CommitIdColumn) - + parentLinks = [] for parent in [str(x) for x in itm.data(0, self.__parentsRole)]: parentLinks.append('<a href="rev:{0}">{0}</a>'.format(parent)) if parentLinks: - parentsStr = self.__parentsTemplate.format( - ", ".join(parentLinks)) + parentsStr = self.__parentsTemplate.format(", ".join(parentLinks)) else: parentsStr = "" - + childLinks = [] for child in [str(x) for x in self.__childrenInfo[commitId]]: childLinks.append('<a href="rev:{0}">{0}</a>'.format(child)) if childLinks: - childrenStr = self.__childrenTemplate.format( - ", ".join(childLinks)) + childrenStr = self.__childrenTemplate.format(", ".join(childLinks)) else: childrenStr = "" - + branchLinks = [] for branch, branchHead in self.__getBranchesForCommit(commitId): - branchLinks.append('<a href="rev:{0}">{1}</a>'.format( - branchHead, branch)) + branchLinks.append( + '<a href="rev:{0}">{1}</a>'.format(branchHead, branch) + ) if branchLinks: - branchesStr = self.__branchesTemplate.format( - ", ".join(branchLinks)) + branchesStr = self.__branchesTemplate.format(", ".join(branchLinks)) else: branchesStr = "" - + tagLinks = [] for tag, tagCommit in self.__getTagsForCommit(commitId): if tagCommit: - tagLinks.append('<a href="rev:{0}">{1}</a>'.format( - tagCommit, tag)) + tagLinks.append('<a href="rev:{0}">{1}</a>'.format(tagCommit, tag)) else: tagLinks.append(tag) if tagLinks: - tagsStr = self.__tagsTemplate.format( - ", ".join(tagLinks)) + tagsStr = self.__tagsTemplate.format(", ".join(tagLinks)) else: tagsStr = "" - + if itm.data(0, self.__messageRole): messageStr = self.__mesageTemplate.format( - "<br/>".join(itm.data(0, self.__messageRole))) + "<br/>".join(itm.data(0, self.__messageRole)) + ) else: messageStr = "" - + html = self.__detailsTemplate.format( commitId, itm.text(self.DateColumn), @@ -1420,13 +1482,13 @@ ) 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 @@ -1437,15 +1499,19 @@ if len(changes) > 0: for change in changes: self.__generateFileItem( - change["action"], change["path"], change["copyfrom"], - change["added"], change["deleted"]) + change["action"], + change["path"], + change["copyfrom"], + change["added"], + change["deleted"], + ) self.__resizeColumnsFiles() self.__resortFiles() - + def __getBranchesForCommit(self, commitId): """ Private method to get all branches reachable from a commit ID. - + @param commitId commit ID to get the branches for @type str @return list of tuples containing the branch name and the associated @@ -1453,36 +1519,38 @@ @rtype tuple of (str, str) """ branches = [] - + args = self.vcs.initCommand("branch") args.append("--list") args.append("--verbose") args.append("--contains") args.append(commitId) - + output = "" process = QProcess() process.setWorkingDirectory(self.repodir) - process.start('git', args) + process.start("git", args) procStarted = process.waitForStarted(5000) if procStarted: finished = process.waitForFinished(30000) if finished and process.exitCode() == 0: - output = str(process.readAllStandardOutput(), - Preferences.getSystem("IOEncoding"), - 'replace') - + output = str( + process.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + "replace", + ) + if output: for line in output.splitlines(): name, commitId = line[2:].split(None, 2)[:2] branches.append((name, commitId)) - + return branches - + def __getTagsForCommit(self, commitId): """ Private method to get all tags reachable from a commit ID. - + @param commitId commit ID to get the tags for @type str @return list of tuples containing the tag name and the associated @@ -1490,105 +1558,113 @@ @rtype tuple of (str, str) """ tags = [] - + args = self.vcs.initCommand("tag") args.append("--list") args.append("--contains") args.append(commitId) - + output = "" process = QProcess() process.setWorkingDirectory(self.repodir) - process.start('git', args) + process.start("git", args) procStarted = process.waitForStarted(5000) if procStarted: finished = process.waitForFinished(30000) if finished and process.exitCode() == 0: - output = str(process.readAllStandardOutput(), - Preferences.getSystem("IOEncoding"), - 'replace') - + output = str( + process.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + "replace", + ) + if output: tagNames = [] for line in output.splitlines(): tagNames.append(line.strip()) - + # determine the commit IDs for the tags for tagName in tagNames: commitId = self.__getCommitForTag(tagName) tags.append((tagName, commitId)) - + return tags - + def __getCommitForTag(self, tag): """ Private method to get the commit id for a tag. - + @param tag tag name (string) @return commit id shortened to 10 characters (string) """ args = self.vcs.initCommand("show") args.append("--abbrev-commit") - args.append("--abbrev={0}".format( - self.vcs.getPlugin().getPreferences("CommitIdLength"))) + args.append( + "--abbrev={0}".format(self.vcs.getPlugin().getPreferences("CommitIdLength")) + ) args.append("--no-patch") args.append(tag) - + output = "" process = QProcess() process.setWorkingDirectory(self.repodir) - process.start('git', args) + process.start("git", args) procStarted = process.waitForStarted(5000) if procStarted: finished = process.waitForFinished(30000) if finished and process.exitCode() == 0: - output = str(process.readAllStandardOutput(), - Preferences.getSystem("IOEncoding"), - 'replace') - + output = str( + process.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + "replace", + ) + if output: for line in output.splitlines(): if line.startswith("commit "): commitId = line.split()[1].strip() return commitId - + return "" - + @pyqtSlot(QPoint) def on_logTree_customContextMenuRequested(self, pos): """ Private slot to show the context menu of the log tree. - + @param pos position of the mouse pointer (QPoint) """ self.__logTreeMenu.popup(self.logTree.mapToGlobal(pos)) - + @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 - len(current.data(0, self.__parentsRole)) > 0 and - (self.logTree.indexOfTopLevelItem(current) < - self.logTree.topLevelItemCount() - 1 or - self.nextButton.isEnabled())) - + current is not None + and len(current.data(0, self.__parentsRole)) > 0 + and ( + self.logTree.indexOfTopLevelItem(current) + < self.logTree.topLevelItemCount() - 1 + or self.nextButton.isEnabled() + ) + ) + @pyqtSlot() def on_logTree_itemSelectionChanged(self): """ @@ -1598,7 +1674,7 @@ self.__updateSbsSelectLabel() self.__updateToolMenuActions() self.__generateDiffs() - + @pyqtSlot() def on_upButton_clicked(self): """ @@ -1607,7 +1683,7 @@ itm = self.logTree.itemAbove(self.logTree.currentItem()) if itm: self.logTree.setCurrentItem(itm) - + @pyqtSlot() def on_downButton_clicked(self): """ @@ -1621,7 +1697,7 @@ if self.nextButton.isEnabled(): self.__addFinishCallback(self.on_downButton_clicked) self.on_nextButton_clicked() - + @pyqtSlot() def on_nextButton_clicked(self): """ @@ -1629,50 +1705,50 @@ """ if self.__skipEntries > 0 and self.nextButton.isEnabled(): self.__getLogEntries(skip=self.__skipEntries) - + @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_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): """ @@ -1680,59 +1756,58 @@ """ if self.__actionMode() == "find": self.__findItem(self.__findBackwards, interactive=True) - + @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) - + ################################################################## ## Tool button menu action methods below ################################################################## - + @pyqtSlot() def __cherryActTriggered(self): """ Private slot to handle the Copy Commits action. """ commits = {} - + for itm in self.logTree.selectedItems(): index = self.logTree.indexOfTopLevelItem(itm) commits[index] = itm.text(self.CommitIdColumn) - + if commits: - pfile = ( - pathlib.Path(ericApp().getObject("Project").getProjectFile()) - ) + pfile = pathlib.Path(ericApp().getObject("Project").getProjectFile()) lastModified = pfile.stat().st_mtime shouldReopen = ( self.vcs.gitCherryPick( self.repodir, - [commits[i] for i in sorted(commits.keys(), reverse=True)] - ) or - pfile.stat().st_mtime != lastModified + [commits[i] for i in sorted(commits.keys(), reverse=True)], + ) + or pfile.stat().st_mtime != lastModified ) 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): """ @@ -1745,7 +1820,7 @@ res = self.vcs.vcsTag(self.repodir, revision=commit, tagName=tag) if res: self.on_refreshButton_clicked() - + @pyqtSlot() def __switchActTriggered(self): """ @@ -1755,8 +1830,9 @@ if len(self.logTree.selectedItems()) == 1: itm = self.logTree.selectedItems()[0] commit = itm.text(self.CommitIdColumn) - branches = [b for b in itm.text(self.BranchColumn).split(", ") - if "/" not in b] + branches = [ + b for b in itm.text(self.BranchColumn).split(", ") if "/" not in b + ] if len(branches) == 1: branch = branches[0] elif len(branches) > 1: @@ -1765,7 +1841,9 @@ self.tr("Switch"), self.tr("Select a branch"), [""] + branches, - 0, False) + 0, + False, + ) if not ok: return else: @@ -1774,27 +1852,25 @@ rev = branch else: rev = commit - pfile = ( - pathlib.Path(ericApp().getObject("Project").getProjectFile()) - ) + pfile = pathlib.Path(ericApp().getObject("Project").getProjectFile()) lastModified = pfile.stat().st_mtime shouldReopen = ( - self.vcs.vcsUpdate(self.repodir, revision=rev) or - pfile.stat().st_mtime != lastModified + self.vcs.vcsUpdate(self.repodir, revision=rev) + or pfile.stat().st_mtime != lastModified ) if shouldReopen: 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 __branchActTriggered(self): """ @@ -1802,10 +1878,12 @@ """ if len(self.logTree.selectedItems()) == 1: from .GitBranchDialog import GitBranchDialog + itm = self.logTree.selectedItems()[0] commit = itm.text(self.CommitIdColumn) - branches = [b for b in itm.text(self.BranchColumn).split(", ") - if "/" not in b] + branches = [ + b for b in itm.text(self.BranchColumn).split(", ") if "/" not in b + ] if len(branches) == 1: branch = branches[0] elif len(branches) > 1: @@ -1814,17 +1892,22 @@ self.tr("Branch"), self.tr("Select a default branch"), [""] + branches, - 0, False) + 0, + False, + ) if not ok: return else: branch = "" res = self.vcs.gitBranch( - self.repodir, revision=commit, branchName=branch, - branchOp=GitBranchDialog.CreateBranch) + self.repodir, + revision=commit, + branchName=branch, + branchOp=GitBranchDialog.CreateBranch, + ) if res: self.on_refreshButton_clicked() - + @pyqtSlot() def __branchSwitchActTriggered(self): """ @@ -1833,10 +1916,12 @@ """ if len(self.logTree.selectedItems()) == 1: from .GitBranchDialog import GitBranchDialog + itm = self.logTree.selectedItems()[0] commit = itm.text(self.CommitIdColumn) - branches = [b for b in itm.text(self.BranchColumn).split(", ") - if "/" not in b] + branches = [ + b for b in itm.text(self.BranchColumn).split(", ") if "/" not in b + ] if len(branches) == 1: branch = branches[0] elif len(branches) > 1: @@ -1845,33 +1930,36 @@ self.tr("Branch & Switch"), self.tr("Select a default branch"), [""] + branches, - 0, False) + 0, + False, + ) if not ok: return else: branch = "" - pfile = ( - pathlib.Path(ericApp().getObject("Project").getProjectFile()) - ) + pfile = pathlib.Path(ericApp().getObject("Project").getProjectFile()) lastModified = pfile.stat().st_mtime res, shouldReopen = self.vcs.gitBranch( - self.repodir, revision=commit, branchName=branch, - branchOp=GitBranchDialog.CreateSwitchBranch) + self.repodir, + revision=commit, + branchName=branch, + branchOp=GitBranchDialog.CreateSwitchBranch, + ) shouldReopen |= pfile.stat().st_mtime != lastModified if res: if shouldReopen: 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 __shortlogActTriggered(self): """ @@ -1881,8 +1969,9 @@ itm = self.logTree.selectedItems()[0] commit = itm.text(self.CommitIdColumn) branch = itm.text(self.BranchColumn).split(", ", 1)[0] - branches = [b for b in itm.text(self.BranchColumn).split(", ") - if "/" not in b] + branches = [ + b for b in itm.text(self.BranchColumn).split(", ") if "/" not in b + ] if len(branches) == 1: branch = branches[0] elif len(branches) > 1: @@ -1891,7 +1980,9 @@ self.tr("Show Short Log"), self.tr("Select a branch"), [""] + branches, - 0, False) + 0, + False, + ) if not ok: return else: @@ -1901,143 +1992,141 @@ else: rev = commit self.vcs.gitShortlog(self.repodir, commit=rev) - + @pyqtSlot() def __describeActTriggered(self): """ Private slot to show the most recent tag reachable from a commit. """ commits = [] - + for itm in self.logTree.selectedItems(): commits.append(itm.text(self.CommitIdColumn)) - + if commits: self.vcs.gitDescribe(self.repodir, commits) - + ################################################################## ## Log context menu action methods below ################################################################## - + @pyqtSlot(bool) def __showCommitterColumns(self, on): """ Private slot to show/hide the committer columns. - + @param on flag indicating the selection state (boolean) """ self.logTree.setColumnHidden(self.CommitterColumn, not on) self.logTree.setColumnHidden(self.CommitDateColumn, not on) self.vcs.getPlugin().setPreferences("ShowCommitterColumns", on) self.__resizeColumnsLog() - + @pyqtSlot(bool) def __showAuthorColumns(self, on): """ Private slot to show/hide the committer columns. - + @param on flag indicating the selection state (boolean) """ self.logTree.setColumnHidden(self.AuthorColumn, not on) self.logTree.setColumnHidden(self.DateColumn, not on) self.vcs.getPlugin().setPreferences("ShowAuthorColumns", on) self.__resizeColumnsLog() - + @pyqtSlot(bool) def __showCommitIdColumn(self, on): """ Private slot to show/hide the commit ID column. - + @param on flag indicating the selection state (boolean) """ self.logTree.setColumnHidden(self.CommitIdColumn, not on) self.vcs.getPlugin().setPreferences("ShowCommitIdColumn", on) self.__resizeColumnsLog() - + @pyqtSlot(bool) def __showBranchesColumn(self, on): """ Private slot to show/hide the branches column. - + @param on flag indicating the selection state (boolean) """ self.logTree.setColumnHidden(self.BranchColumn, not on) self.vcs.getPlugin().setPreferences("ShowBranchesColumn", on) self.__resizeColumnsLog() - + @pyqtSlot(bool) def __showTagsColumn(self, on): """ Private slot to show/hide the tags column. - + @param on flag indicating the selection state (boolean) """ self.logTree.setColumnHidden(self.TagsColumn, not on) self.vcs.getPlugin().setPreferences("ShowTagsColumn", on) self.__resizeColumnsLog() - + ################################################################## ## Search and filter methods below ################################################################## - + 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.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) @@ -2047,17 +2136,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: # Filter based on complete subject text @@ -2071,17 +2158,18 @@ 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()), + ) + ################################################################## ## Commit navigation methods below ################################################################## - + def __commitIdClicked(self, url): """ Private slot to handle the anchorClicked signal of the changeset details pane. - + @param url URL that was clicked @type QUrl """ @@ -2089,7 +2177,8 @@ # a commit ID was clicked, show the respective item commitId = url.path() items = self.logTree.findItems( - commitId, Qt.MatchFlag.MatchStartsWith, self.CommitIdColumn) + commitId, Qt.MatchFlag.MatchStartsWith, self.CommitIdColumn + ) if items: itm = items[0] if itm.isHidden(): @@ -2098,18 +2187,17 @@ else: # load the next batch and try again if self.nextButton.isEnabled(): - self.__addFinishCallback( - lambda: self.__commitIdClicked(url)) + self.__addFinishCallback(lambda: self.__commitIdClicked(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 """ @@ -2118,7 +2206,7 @@ self.diffSelectLabel.clear() with contextlib.suppress(AttributeError): self.diffHighlighter.regenerateRules() - + selectedItems = self.logTree.selectedItems() if len(selectedItems) == 1: currentItem = selectedItems[0] @@ -2126,11 +2214,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) + ) commit1 = parents[parent - 1] - + self.__diffGenerator.start(self.__filename, [commit1, commit2]) - + if len(parents) > 1: parentLinks = [] for index in range(1, len(parents) + 1): @@ -2138,38 +2227,38 @@ parentLinks.append(" {0} ".format(index)) else: parentLinks.append( - '<a href="diff:{0}"> {0} </a>' - .format(index)) + '<a href="diff:{0}"> {0} </a>'.format(index) + ) self.diffSelectLabel.setText( - self.tr('Diff to Parent {0}') - .format(" ".join(parentLinks))) + self.tr("Diff to Parent {0}").format(" ".join(parentLinks)) + ) elif len(selectedItems) == 2: commit2 = selectedItems[0].text(self.CommitIdColumn) commit1 = selectedItems[1].text(self.CommitIdColumn) index2 = self.logTree.indexOfTopLevelItem(selectedItems[0]) index1 = self.logTree.indexOfTopLevelItem(selectedItems[1]) - + if index2 < index1: # swap to always compare old to new commit1, commit2 = commit2, commit1 - + self.__diffGenerator.start(self.__filename, [commit1, commit2]) - + 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)) - + fileSeparators = self.__mergeFileSeparators(fileSeparators) if self.__diffUpdatesFiles: for oldFileName, newFileName, lineNumber, _ in fileSeparators: @@ -2179,7 +2268,8 @@ item = QTreeWidgetItem(self.filesTree, ["", newFileName]) else: item = QTreeWidgetItem( - self.filesTree, ["", newFileName, "", "", oldFileName]) + self.filesTree, ["", newFileName, "", "", oldFileName] + ) item.setData(0, self.__diffFileLineRole, lineNumber) self.__resizeColumnsFiles() self.__resortFiles() @@ -2188,20 +2278,20 @@ for fileName in (oldFileName, newFileName): if fileName != "/dev/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() - + def __mergeFileSeparators(self, fileSeparators): """ Private method to merge the file separator entries. - + @param fileSeparators list of file separator entries to be merged @return merged list of file separator entries """ @@ -2215,12 +2305,12 @@ if pos2 != -2: separators[(oldFile, newFile)][3] = pos2 return list(separators.values()) - + @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) """ @@ -2243,20 +2333,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 """ @@ -2266,43 +2359,44 @@ 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: 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] @@ -2312,12 +2406,14 @@ 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: @@ -2325,17 +2421,19 @@ f.write(eol) 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 """ @@ -2343,5 +2441,4 @@ scheme, path = link.split(":", 1) if scheme == "sbsdiff" and "_" in path: commit1, commit2 = path.split("_", 1) - self.vcs.vcsSbsDiff(self.__filename, - revisions=(commit1, commit2)) + self.vcs.vcsSbsDiff(self.__filename, revisions=(commit1, commit2))