--- a/src/eric7/Plugins/VcsPlugins/vcsGit/GitStatusDialog.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/Plugins/VcsPlugins/vcsGit/GitStatusDialog.py Wed Jul 13 14:55:47 2022 +0200 @@ -15,8 +15,13 @@ from PyQt6.QtCore import pyqtSlot, Qt, QProcess, QTimer, QSize from PyQt6.QtGui import QTextCursor from PyQt6.QtWidgets import ( - QWidget, QDialogButtonBox, QMenu, QHeaderView, QTreeWidgetItem, QLineEdit, - QInputDialog + QWidget, + QDialogButtonBox, + QMenu, + QHeaderView, + QTreeWidgetItem, + QLineEdit, + QInputDialog, ) from EricWidgets.EricApplication import ericApp @@ -40,36 +45,35 @@ Class implementing a dialog to show the output of the git status command process. """ + ConflictStates = ["AA", "AU", "DD", "DU", "UA", "UD", "UU"] - + ConflictRole = Qt.ItemDataRole.UserRole - + 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) - + self.__toBeCommittedColumn = 0 self.__statusWorkColumn = 1 self.__statusIndexColumn = 2 self.__pathColumn = 3 self.__lastColumn = self.statusList.columnCount() - + self.refreshButton = self.buttonBox.addButton( - self.tr("Refresh"), QDialogButtonBox.ButtonRole.ActionRole) - self.refreshButton.setToolTip( - self.tr("Press to refresh the status display")) + self.tr("Refresh"), QDialogButtonBox.ButtonRole.ActionRole + ) + self.refreshButton.setToolTip(self.tr("Press to refresh the status display")) self.refreshButton.setEnabled(False) - 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.diff = None self.vcs = vcs self.vcs.committed.connect(self.__committed) @@ -77,106 +81,111 @@ self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) - + self.errorGroup.hide() self.inputGroup.hide() - + self.vDiffSplitter.setStretchFactor(0, 2) self.vDiffSplitter.setStretchFactor(0, 2) self.vDiffSplitter.setSizes([400, 400]) self.__hDiffSplitterState = None self.__vDiffSplitterState = None - + self.statusList.headerItem().setText(self.__lastColumn, "") self.statusList.header().setSortIndicator( - self.__pathColumn, Qt.SortOrder.AscendingOrder) - + self.__pathColumn, Qt.SortOrder.AscendingOrder + ) + font = Preferences.getEditorOtherFonts("MonospacedFont") self.lDiffEdit.document().setDefaultFont(font) self.rDiffEdit.document().setDefaultFont(font) - self.lDiffEdit.customContextMenuRequested.connect( - self.__showLDiffContextMenu) - self.rDiffEdit.customContextMenuRequested.connect( - self.__showRDiffContextMenu) - + self.lDiffEdit.customContextMenuRequested.connect(self.__showLDiffContextMenu) + self.rDiffEdit.customContextMenuRequested.connect(self.__showRDiffContextMenu) + self.__lDiffMenu = QMenu() self.__stageLinesAct = self.__lDiffMenu.addAction( UI.PixmapCache.getIcon("vcsAdd"), self.tr("Stage Selected Lines"), - self.__stageHunkOrLines) + self.__stageHunkOrLines, + ) self.__revertLinesAct = self.__lDiffMenu.addAction( UI.PixmapCache.getIcon("vcsRevert"), self.tr("Revert Selected Lines"), - self.__revertHunkOrLines) + self.__revertHunkOrLines, + ) self.__stageHunkAct = self.__lDiffMenu.addAction( UI.PixmapCache.getIcon("vcsAdd"), self.tr("Stage Hunk"), - self.__stageHunkOrLines) + self.__stageHunkOrLines, + ) self.__revertHunkAct = self.__lDiffMenu.addAction( UI.PixmapCache.getIcon("vcsRevert"), self.tr("Revert Hunk"), - self.__revertHunkOrLines) - + self.__revertHunkOrLines, + ) + self.__rDiffMenu = QMenu() self.__unstageLinesAct = self.__rDiffMenu.addAction( UI.PixmapCache.getIcon("vcsRemove"), self.tr("Unstage Selected Lines"), - self.__unstageHunkOrLines) + self.__unstageHunkOrLines, + ) self.__unstageHunkAct = self.__rDiffMenu.addAction( UI.PixmapCache.getIcon("vcsRemove"), self.tr("Unstage Hunk"), - self.__unstageHunkOrLines) - + self.__unstageHunkOrLines, + ) + self.lDiffHighlighter = GitDiffHighlighter(self.lDiffEdit.document()) self.rDiffHighlighter = GitDiffHighlighter(self.rDiffEdit.document()) - + self.lDiffParser = None self.rDiffParser = None - + self.__selectedName = "" - + self.__diffGenerator = GitDiffGenerator(vcs, self) self.__diffGenerator.finished.connect(self.__generatorFinished) - + self.modifiedIndicators = [ - self.tr('added'), - self.tr('copied'), - self.tr('deleted'), - self.tr('modified'), - self.tr('renamed'), + self.tr("added"), + self.tr("copied"), + self.tr("deleted"), + self.tr("modified"), + self.tr("renamed"), ] self.modifiedOnlyIndicators = [ - self.tr('modified'), + self.tr("modified"), ] - + self.unversionedIndicators = [ - self.tr('not tracked'), + self.tr("not tracked"), ] - + self.missingIndicators = [ - self.tr('deleted'), + self.tr("deleted"), ] - + self.unmergedIndicators = [ - self.tr('unmerged'), + self.tr("unmerged"), ] self.status = { - ' ': self.tr("unmodified"), - 'A': self.tr('added'), - 'C': self.tr('copied'), - 'D': self.tr('deleted'), - 'M': self.tr('modified'), - 'R': self.tr('renamed'), - 'U': self.tr('unmerged'), - '?': self.tr('not tracked'), - '!': self.tr('ignored'), + " ": self.tr("unmodified"), + "A": self.tr("added"), + "C": self.tr("copied"), + "D": self.tr("deleted"), + "M": self.tr("modified"), + "R": self.tr("renamed"), + "U": self.tr("unmerged"), + "?": self.tr("not tracked"), + "!": self.tr("ignored"), } - + self.__ioEncoding = Preferences.getSystem("IOEncoding") - + self.__initActionsMenu() - + def __initActionsMenu(self): """ Private method to initialize the actions menu. @@ -185,209 +194,216 @@ self.__actionsMenu.setTearOffEnabled(True) self.__actionsMenu.setToolTipsVisible(True) self.__actionsMenu.aboutToShow.connect(self.__showActionsMenu) - + self.__commitAct = self.__actionsMenu.addAction( - self.tr("Commit"), self.__commit) + self.tr("Commit"), self.__commit + ) self.__commitAct.setToolTip(self.tr("Commit the selected changes")) - self.__amendAct = self.__actionsMenu.addAction( - self.tr("Amend"), self.__amend) - self.__amendAct.setToolTip(self.tr( - "Amend the latest commit with the selected changes")) + self.__amendAct = self.__actionsMenu.addAction(self.tr("Amend"), self.__amend) + self.__amendAct.setToolTip( + self.tr("Amend the latest commit with the selected changes") + ) self.__commitSelectAct = self.__actionsMenu.addAction( - self.tr("Select all for commit"), self.__commitSelectAll) + self.tr("Select all for commit"), self.__commitSelectAll + ) self.__commitDeselectAct = self.__actionsMenu.addAction( - self.tr("Unselect all from commit"), self.__commitDeselectAll) - + self.tr("Unselect all from commit"), self.__commitDeselectAll + ) + self.__actionsMenu.addSeparator() - self.__addAct = self.__actionsMenu.addAction( - self.tr("Add"), self.__add) + self.__addAct = self.__actionsMenu.addAction(self.tr("Add"), self.__add) self.__addAct.setToolTip(self.tr("Add the selected files")) self.__stageAct = self.__actionsMenu.addAction( - self.tr("Stage changes"), self.__stage) - self.__stageAct.setToolTip(self.tr( - "Stages all changes of the selected files")) + self.tr("Stage changes"), self.__stage + ) + self.__stageAct.setToolTip(self.tr("Stages all changes of the selected files")) self.__unstageAct = self.__actionsMenu.addAction( - self.tr("Unstage changes"), self.__unstage) - self.__unstageAct.setToolTip(self.tr( - "Unstages all changes of the selected files")) - + self.tr("Unstage changes"), self.__unstage + ) + self.__unstageAct.setToolTip( + self.tr("Unstages all changes of the selected files") + ) + self.__actionsMenu.addSeparator() - + self.__diffAct = self.__actionsMenu.addAction( - self.tr("Differences"), self.__diff) - self.__diffAct.setToolTip(self.tr( - "Shows the differences of the selected entry in a" - " separate dialog")) + self.tr("Differences"), self.__diff + ) + self.__diffAct.setToolTip( + self.tr( + "Shows the differences of the selected entry in a" " separate dialog" + ) + ) self.__sbsDiffAct = self.__actionsMenu.addAction( - self.tr("Differences Side-By-Side"), self.__sbsDiff) - self.__sbsDiffAct.setToolTip(self.tr( - "Shows the differences of the selected entry side-by-side in" - " a separate dialog")) - - self.__actionsMenu.addSeparator() - - self.__revertAct = self.__actionsMenu.addAction( - self.tr("Revert"), self.__revert) - self.__revertAct.setToolTip(self.tr( - "Reverts the changes of the selected files")) - + self.tr("Differences Side-By-Side"), self.__sbsDiff + ) + self.__sbsDiffAct.setToolTip( + self.tr( + "Shows the differences of the selected entry side-by-side in" + " a separate dialog" + ) + ) + self.__actionsMenu.addSeparator() - + + self.__revertAct = self.__actionsMenu.addAction( + self.tr("Revert"), self.__revert + ) + self.__revertAct.setToolTip( + self.tr("Reverts the changes of the selected files") + ) + + self.__actionsMenu.addSeparator() + self.__forgetAct = self.__actionsMenu.addAction( - self.tr("Forget Missing"), self.__forget) - self.__forgetAct.setToolTip(self.tr( - "Forgets about the selected missing files")) + self.tr("Forget Missing"), self.__forget + ) + self.__forgetAct.setToolTip(self.tr("Forgets about the selected missing files")) self.__restoreAct = self.__actionsMenu.addAction( - self.tr("Restore Missing"), self.__restoreMissing) - self.__restoreAct.setToolTip(self.tr( - "Restores the selected missing files")) - + self.tr("Restore Missing"), self.__restoreMissing + ) + self.__restoreAct.setToolTip(self.tr("Restores the selected missing files")) + self.__actionsMenu.addSeparator() - + self.__editAct = self.__actionsMenu.addAction( - self.tr("Edit Conflict"), self.__editConflict) - self.__editAct.setToolTip(self.tr( - "Edit the selected conflicting file")) - + self.tr("Edit Conflict"), self.__editConflict + ) + self.__editAct.setToolTip(self.tr("Edit the selected conflicting file")) + self.__actionsMenu.addSeparator() - + act = self.__actionsMenu.addAction( - self.tr("Adjust column sizes"), self.__resizeColumns) - act.setToolTip(self.tr( - "Adjusts the width of all columns to their contents")) - - self.actionsButton.setIcon( - UI.PixmapCache.getIcon("actionsToolButton")) + self.tr("Adjust column sizes"), self.__resizeColumns + ) + act.setToolTip(self.tr("Adjusts the width of all columns to their contents")) + + self.actionsButton.setIcon(UI.PixmapCache.getIcon("actionsToolButton")) self.actionsButton.setMenu(self.__actionsMenu) - + 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( - "StatusDialogGeometry", self.saveGeometry()) + + self.vcs.getPlugin().setPreferences("StatusDialogGeometry", self.saveGeometry()) self.vcs.getPlugin().setPreferences( - "StatusDialogSplitterStates", [ - self.vDiffSplitter.saveState(), - self.hDiffSplitter.saveState() - ] + "StatusDialogSplitterStates", + [self.vDiffSplitter.saveState(), self.hDiffSplitter.saveState()], ) - + e.accept() - + def show(self): """ Public slot to show the dialog. """ super().show() - - geom = self.vcs.getPlugin().getPreferences( - "StatusDialogGeometry") + + geom = self.vcs.getPlugin().getPreferences("StatusDialogGeometry") if geom.isEmpty(): s = QSize(900, 600) self.resize(s) else: self.restoreGeometry(geom) - - states = self.vcs.getPlugin().getPreferences( - "StatusDialogSplitterStates") + + states = self.vcs.getPlugin().getPreferences("StatusDialogSplitterStates") if len(states) == 2: # we have two splitters self.vDiffSplitter.restoreState(states[0]) self.hDiffSplitter.restoreState(states[1]) - + def __resort(self): """ Private method to resort the tree. """ self.statusList.sortItems( - self.statusList.sortColumn(), - self.statusList.header().sortIndicatorOrder()) - + self.statusList.sortColumn(), self.statusList.header().sortIndicatorOrder() + ) + def __resizeColumns(self): """ Private method to resize the list columns. """ - self.statusList.header().resizeSections( - QHeaderView.ResizeMode.ResizeToContents) + self.statusList.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents) self.statusList.header().setStretchLastSection(True) - + def __generateItem(self, status, path): """ Private method to generate a status item in the status list. - + @param status status indicator (string) @param path path of the file or directory (string) """ statusWorkText = self.status[status[1]] statusIndexText = self.status[status[0]] - itm = QTreeWidgetItem(self.statusList, [ - "", - statusWorkText, - statusIndexText, - path, - ]) - - itm.setTextAlignment(self.__statusWorkColumn, - Qt.AlignmentFlag.AlignHCenter) - itm.setTextAlignment(self.__statusIndexColumn, - Qt.AlignmentFlag.AlignHCenter) - itm.setTextAlignment(self.__pathColumn, - Qt.AlignmentFlag.AlignLeft) - + itm = QTreeWidgetItem( + self.statusList, + [ + "", + statusWorkText, + statusIndexText, + path, + ], + ) + + itm.setTextAlignment(self.__statusWorkColumn, Qt.AlignmentFlag.AlignHCenter) + itm.setTextAlignment(self.__statusIndexColumn, Qt.AlignmentFlag.AlignHCenter) + itm.setTextAlignment(self.__pathColumn, Qt.AlignmentFlag.AlignLeft) + if ( - status not in self.ConflictStates + ["??", "!!"] and - statusIndexText in self.modifiedIndicators + status not in self.ConflictStates + ["??", "!!"] + and statusIndexText in self.modifiedIndicators ): itm.setFlags(itm.flags() | Qt.ItemFlag.ItemIsUserCheckable) - itm.setCheckState(self.__toBeCommittedColumn, - Qt.CheckState.Checked) + itm.setCheckState(self.__toBeCommittedColumn, Qt.CheckState.Checked) else: itm.setFlags(itm.flags() & ~Qt.ItemFlag.ItemIsUserCheckable) - + if statusWorkText not in self.__statusFilters: self.__statusFilters.append(statusWorkText) if statusIndexText not in self.__statusFilters: self.__statusFilters.append(statusIndexText) - + if status in self.ConflictStates: - itm.setIcon(self.__statusWorkColumn, - UI.PixmapCache.getIcon( - os.path.join("VcsPlugins", "vcsGit", "icons", - "conflict.svg"))) + itm.setIcon( + self.__statusWorkColumn, + UI.PixmapCache.getIcon( + os.path.join("VcsPlugins", "vcsGit", "icons", "conflict.svg") + ), + ) itm.setData(0, self.ConflictRole, status in self.ConflictStates) - + def start(self, fn): """ Public slot to start the git status command. - + @param fn filename(s)/directoryname(s) to show the status of (string or list of strings) """ self.errorGroup.hide() self.intercept = False self.args = fn - + self.__ioEncoding = Preferences.getSystem("IOEncoding") - + self.statusFilterCombo.clear() self.__statusFilters = [] self.statusList.clear() - - self.setWindowTitle(self.tr('Git Status')) - + + self.setWindowTitle(self.tr("Git Status")) + args = self.vcs.initCommand("status") - args.append('--porcelain') + args.append("--porcelain") args.append("--") if isinstance(fn, list): self.dname, fnames = self.vcs.splitPathList(fn) @@ -395,149 +411,143 @@ else: self.dname, fname = self.vcs.splitPath(fn) args.append(fn) - + # find the root of the repo self.__repodir = self.dname - while not os.path.isdir( - os.path.join(self.__repodir, self.vcs.adminDir)): + 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.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"), + ) else: - 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) - + 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.inputGroup.setEnabled(False) self.inputGroup.hide() self.refreshButton.setEnabled(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.buttonBox.button( - QDialogButtonBox.StandardButton.Close).setFocus( - Qt.FocusReason.OtherFocusReason) - + + 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).setFocus( + Qt.FocusReason.OtherFocusReason + ) + self.__statusFilters.sort() self.__statusFilters.insert(0, "<{0}>".format(self.tr("all"))) self.statusFilterCombo.addItems(self.__statusFilters) - + self.__resort() self.__resizeColumns() - + self.__refreshDiff() - + 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.__finish() elif button == self.refreshButton: self.on_refreshButton_clicked() - + 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.__finish() - + def __readStdout(self): """ Private slot to handle the readyReadStandardOutput signal. - + It reads the output of the process, formats it and inserts it into the contents pane. """ if self.process is not None: self.process.setReadChannel(QProcess.ProcessChannel.StandardOutput) - + while self.process.canReadLine(): - line = str(self.process.readLine(), self.__ioEncoding, - 'replace') - + line = str(self.process.readLine(), self.__ioEncoding, "replace") + status = line[:2] path = line[3:].strip().split(" -> ")[-1].strip('"') self.__generateItem(status, path) - + 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(), - self.__ioEncoding, 'replace') + s = str(self.process.readAllStandardError(), self.__ioEncoding, "replace") self.errorGroup.show() self.errors.insertPlainText(s) self.errors.ensureCursorVisible() - + # show input in case the process asked for some input self.inputGroup.setEnabled(True) self.inputGroup.show() - + 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): """ @@ -545,30 +555,30 @@ """ inputTxt = self.input.text() inputTxt += os.linesep - + if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(inputTxt) self.errors.ensureCursorVisible() - + 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: @@ -576,7 +586,7 @@ evt.accept() return super().keyPressEvent(evt) - + @pyqtSlot() def on_refreshButton_clicked(self): """ @@ -587,14 +597,14 @@ self.__selectedName = selectedItems[0].text(self.__pathColumn) else: self.__selectedName = "" - + self.start(self.args) - + @pyqtSlot(int) def on_statusFilterCombo_activated(self, index): """ Private slot to react to the selection of a status filter. - + @param index index of the selected entry @type int """ @@ -607,21 +617,21 @@ for topIndex in range(self.statusList.topLevelItemCount()): topItem = self.statusList.topLevelItem(topIndex) topItem.setHidden( - topItem.text(self.__statusWorkColumn) != txt and - topItem.text(self.__statusIndexColumn) != txt + topItem.text(self.__statusWorkColumn) != txt + and topItem.text(self.__statusIndexColumn) != txt ) - + @pyqtSlot() def on_statusList_itemSelectionChanged(self): """ Private slot to act upon changes of selected items. """ self.__generateDiffs() - + ########################################################################### ## Menu handling methods ########################################################################### - + def __showActionsMenu(self): """ Private slot to prepare the actions button menu before it is shown. @@ -649,36 +659,38 @@ self.__forgetAct.setEnabled(missing) self.__restoreAct.setEnabled(missing) self.__editAct.setEnabled(conflicting == 1) - + def __amend(self): """ Private slot to handle the Amend context menu entry. """ self.__commit(amend=True) - + def __commit(self, amend=False): """ Private slot to handle the Commit context menu entry. - + @param amend flag indicating to perform an amend operation (boolean) """ - names = [os.path.join(self.dname, itm.text(self.__pathColumn)) - for itm in self.__getCommitableItems()] + names = [ + os.path.join(self.dname, itm.text(self.__pathColumn)) + for itm in self.__getCommitableItems() + ] if not names: EricMessageBox.information( self, self.tr("Commit"), - self.tr("""There are no entries selected to be""" - """ committed.""")) + self.tr("""There are no entries selected to be""" """ committed."""), + ) return - + if Preferences.getVCS("AutoSaveFiles"): vm = ericApp().getObject("ViewManager") for name in names: vm.saveEditor(name) self.vcs.vcsCommit(names, commitAll=False, amend=amend) # staged changes - + def __committed(self): """ Private slot called after the commit has finished. @@ -686,97 +698,111 @@ if self.isVisible(): self.on_refreshButton_clicked() self.vcs.checkVCSStatus() - + def __commitSelectAll(self): """ Private slot to select all entries for commit. """ self.__commitSelect(True) - + def __commitDeselectAll(self): """ Private slot to deselect all entries from commit. """ self.__commitSelect(False) - + def __add(self): """ Private slot to handle the Add context menu entry. """ - names = [os.path.join(self.dname, itm.text(self.__pathColumn)) - for itm in self.__getUnversionedItems()] + names = [ + os.path.join(self.dname, itm.text(self.__pathColumn)) + for itm in self.__getUnversionedItems() + ] if not names: EricMessageBox.information( self, self.tr("Add"), - self.tr("""There are no unversioned entries""" - """ available/selected.""")) + self.tr( + """There are no unversioned entries""" """ available/selected.""" + ), + ) return - + self.vcs.vcsAdd(names) self.on_refreshButton_clicked() - + project = ericApp().getObject("Project") for name in names: project.getModel().updateVCSStatus(name) self.vcs.checkVCSStatus() - + def __stage(self): """ Private slot to handle the Stage context menu entry. """ - names = [os.path.join(self.dname, itm.text(self.__pathColumn)) - for itm in self.__getStageableItems()] + names = [ + os.path.join(self.dname, itm.text(self.__pathColumn)) + for itm in self.__getStageableItems() + ] if not names: EricMessageBox.information( self, self.tr("Stage"), - self.tr("""There are no stageable entries""" - """ available/selected.""")) + self.tr( + """There are no stageable entries""" """ available/selected.""" + ), + ) return - + self.vcs.vcsAdd(names) self.on_refreshButton_clicked() - + project = ericApp().getObject("Project") for name in names: project.getModel().updateVCSStatus(name) self.vcs.checkVCSStatus() - + def __unstage(self): """ Private slot to handle the Unstage context menu entry. """ - names = [os.path.join(self.dname, itm.text(self.__pathColumn)) - for itm in self.__getUnstageableItems()] + names = [ + os.path.join(self.dname, itm.text(self.__pathColumn)) + for itm in self.__getUnstageableItems() + ] if not names: EricMessageBox.information( self, self.tr("Unstage"), - self.tr("""There are no unstageable entries""" - """ available/selected.""")) + self.tr( + """There are no unstageable entries""" """ available/selected.""" + ), + ) return - + self.vcs.gitUnstage(names) self.on_refreshButton_clicked() - + project = ericApp().getObject("Project") for name in names: project.getModel().updateVCSStatus(name) self.vcs.checkVCSStatus() - + def __forget(self): """ Private slot to handle the Forget Missing context menu entry. """ - names = [os.path.join(self.dname, itm.text(self.__pathColumn)) - for itm in self.__getMissingItems()] + names = [ + os.path.join(self.dname, itm.text(self.__pathColumn)) + for itm in self.__getMissingItems() + ] if not names: EricMessageBox.information( self, self.tr("Forget Missing"), - self.tr("""There are no missing entries""" - """ available/selected.""")) + self.tr("""There are no missing entries""" """ available/selected."""), + ) return self.vcs.vcsRemove(names, stageOnly=True) @@ -786,14 +812,19 @@ """ Private slot to handle the Revert context menu entry. """ - names = [os.path.join(self.dname, itm.text(self.__pathColumn)) - for itm in self.__getStageableItems()] + names = [ + os.path.join(self.dname, itm.text(self.__pathColumn)) + for itm in self.__getStageableItems() + ] if not names: EricMessageBox.information( self, self.tr("Revert"), - self.tr("""There are no uncommitted, unstaged changes""" - """ available/selected.""")) + self.tr( + """There are no uncommitted, unstaged changes""" + """ available/selected.""" + ), + ) return self.vcs.vcsRevert(names) @@ -810,20 +841,22 @@ """ Private slot to handle the Restore Missing context menu entry. """ - names = [os.path.join(self.dname, itm.text(self.__pathColumn)) - for itm in self.__getMissingItems()] + names = [ + os.path.join(self.dname, itm.text(self.__pathColumn)) + for itm in self.__getMissingItems() + ] if not names: EricMessageBox.information( self, self.tr("Restore Missing"), - self.tr("""There are no missing entries""" - """ available/selected.""")) + self.tr("""There are no missing entries""" """ available/selected."""), + ) return self.vcs.vcsRevert(names) self.on_refreshButton_clicked() self.vcs.checkVCSStatus() - + def __editConflict(self): """ Private slot to handle the Edit Conflict context menu entry. @@ -837,38 +870,45 @@ """ Private slot to handle the Diff context menu entry. """ - namesW = [os.path.join(self.dname, itm.text(self.__pathColumn)) - for itm in self.__getStageableItems()] - namesS = [os.path.join(self.dname, itm.text(self.__pathColumn)) - for itm in self.__getUnstageableItems()] + namesW = [ + os.path.join(self.dname, itm.text(self.__pathColumn)) + for itm in self.__getStageableItems() + ] + namesS = [ + os.path.join(self.dname, itm.text(self.__pathColumn)) + for itm in self.__getUnstageableItems() + ] if not namesW and not namesS: EricMessageBox.information( self, self.tr("Differences"), - self.tr("""There are no uncommitted changes""" - """ available/selected.""")) + self.tr( + """There are no uncommitted changes""" """ available/selected.""" + ), + ) return - + diffMode = "work2stage2repo" names = namesW + namesS - + if self.diff is None: from .GitDiffDialog import GitDiffDialog + self.diff = GitDiffDialog(self.vcs) self.diff.show() self.diff.start(names, diffMode=diffMode, refreshable=True) - + def __sbsDiff(self): """ Private slot to handle the Side-By-Side Diff context menu entry. """ itm = self.__getModifiedOnlyItems()[0] - workModified = (itm.text(self.__statusWorkColumn) in - self.modifiedOnlyIndicators) - stageModified = (itm.text(self.__statusIndexColumn) in - self.modifiedOnlyIndicators) + workModified = itm.text(self.__statusWorkColumn) in self.modifiedOnlyIndicators + stageModified = ( + itm.text(self.__statusIndexColumn) in self.modifiedOnlyIndicators + ) names = [os.path.join(self.dname, itm.text(self.__pathColumn))] - + if workModified and stageModified: # select from all three variants messages = [ @@ -881,10 +921,12 @@ self.tr("Differences Side-by-Side"), self.tr("Select the compare method."), messages, - 0, False) + 0, + False, + ) if not ok: return - + if result == messages[0]: revisions = ["", ""] elif result == messages[1]: @@ -902,89 +944,86 @@ self.tr("Differences Side-by-Side"), self.tr("Select the compare method."), messages, - 0, False) + 0, + False, + ) if not ok: return - + if result == messages[0]: revisions = ["", ""] else: revisions = ["HEAD", ""] else: revisions = ["HEAD", "Stage"] - + self.vcs.vcsSbsDiff(names[0], revisions=revisions) - + def __getCommitableItems(self): """ Private method to retrieve all entries the user wants to commit. - + @return list of all items, the user has checked """ commitableItems = [] for index in range(self.statusList.topLevelItemCount()): itm = self.statusList.topLevelItem(index) - if ( - itm.checkState(self.__toBeCommittedColumn) == - Qt.CheckState.Checked - ): + if itm.checkState(self.__toBeCommittedColumn) == Qt.CheckState.Checked: commitableItems.append(itm) return commitableItems - + def __getCommitableUnselectedItems(self): """ Private method to retrieve all entries the user may commit but hasn't selected. - + @return list of all items, the user has not checked """ items = [] for index in range(self.statusList.topLevelItemCount()): itm = self.statusList.topLevelItem(index) if ( - (itm.flags() & Qt.ItemFlag.ItemIsUserCheckable == - Qt.ItemFlag.ItemIsUserCheckable) and - itm.checkState(self.__toBeCommittedColumn) == - Qt.CheckState.Unchecked - ): + itm.flags() & Qt.ItemFlag.ItemIsUserCheckable + == Qt.ItemFlag.ItemIsUserCheckable + ) and itm.checkState(self.__toBeCommittedColumn) == Qt.CheckState.Unchecked: items.append(itm) return items - + def __getModifiedItems(self): """ Private method to retrieve all entries, that have a modified status. - + @return list of all items with a modified status """ modifiedItems = [] for itm in self.statusList.selectedItems(): - if (itm.text(self.__statusWorkColumn) in - self.modifiedIndicators or - itm.text(self.__statusIndexColumn) in - self.modifiedIndicators): + if ( + itm.text(self.__statusWorkColumn) in self.modifiedIndicators + or itm.text(self.__statusIndexColumn) in self.modifiedIndicators + ): modifiedItems.append(itm) return modifiedItems - + def __getModifiedOnlyItems(self): """ Private method to retrieve all entries, that have a modified status. - + @return list of all items with a modified status """ modifiedItems = [] for itm in self.statusList.selectedItems(): - if (itm.text(self.__statusWorkColumn) in - self.modifiedOnlyIndicators or - itm.text(self.__statusIndexColumn) in - self.modifiedOnlyIndicators): + if ( + itm.text(self.__statusWorkColumn) in self.modifiedOnlyIndicators + or itm.text(self.__statusIndexColumn) in self.modifiedOnlyIndicators + ): modifiedItems.append(itm) return modifiedItems - + def __getUnversionedItems(self): """ Private method to retrieve all entries, that have an unversioned status. - + @return list of all items with an unversioned status """ unversionedItems = [] @@ -992,28 +1031,28 @@ if itm.text(self.__statusWorkColumn) in self.unversionedIndicators: unversionedItems.append(itm) return unversionedItems - + def __getStageableItems(self): """ Private method to retrieve all entries, that have a stageable status. - + @return list of all items with a stageable status """ stageableItems = [] for itm in self.statusList.selectedItems(): if ( - itm.text(self.__statusWorkColumn) in - self.modifiedIndicators + self.unmergedIndicators + itm.text(self.__statusWorkColumn) + in self.modifiedIndicators + self.unmergedIndicators ): stageableItems.append(itm) return stageableItems - + def __getUnstageableItems(self): """ Private method to retrieve all entries, that have an unstageable status. - + @return list of all items with an unstageable status """ unstageableItems = [] @@ -1021,11 +1060,11 @@ if itm.text(self.__statusIndexColumn) in self.modifiedIndicators: unstageableItems.append(itm) return unstageableItems - + def __getMissingItems(self): """ Private method to retrieve all entries, that have a missing status. - + @return list of all items with a missing status """ missingItems = [] @@ -1033,11 +1072,11 @@ if itm.text(self.__statusWorkColumn) in self.missingIndicators: missingItems.append(itm) return missingItems - + def __getConflictingItems(self): """ Private method to retrieve all entries, that have a conflict status. - + @return list of all items with a conflict status """ conflictingItems = [] @@ -1045,30 +1084,30 @@ if itm.data(0, self.ConflictRole): conflictingItems.append(itm) return conflictingItems - + def __commitSelect(self, selected): """ Private slot to select or deselect all entries. - + @param selected commit selection state to be set (boolean) """ for index in range(self.statusList.topLevelItemCount()): itm = self.statusList.topLevelItem(index) if ( - itm.flags() & Qt.ItemFlag.ItemIsUserCheckable == - Qt.ItemFlag.ItemIsUserCheckable + itm.flags() & Qt.ItemFlag.ItemIsUserCheckable + == Qt.ItemFlag.ItemIsUserCheckable ): if selected: - itm.setCheckState(self.__toBeCommittedColumn, - Qt.CheckState.Checked) + itm.setCheckState(self.__toBeCommittedColumn, Qt.CheckState.Checked) else: - itm.setCheckState(self.__toBeCommittedColumn, - Qt.CheckState.Unchecked) - + itm.setCheckState( + self.__toBeCommittedColumn, Qt.CheckState.Unchecked + ) + ########################################################################### ## Diff handling methods below ########################################################################### - + def __generateDiffs(self): """ Private slot to generate diff outputs for the selected item. @@ -1078,19 +1117,18 @@ with contextlib.suppress(AttributeError): self.lDiffHighlighter.regenerateRules() self.rDiffHighlighter.regenerateRules() - + selectedItems = self.statusList.selectedItems() if len(selectedItems) == 1: - fn = os.path.join(self.dname, - selectedItems[0].text(self.__pathColumn)) + fn = os.path.join(self.dname, selectedItems[0].text(self.__pathColumn)) self.__diffGenerator.start(fn, diffMode="work2stage2repo") - + def __generatorFinished(self): """ Private slot connected to the finished signal of the diff generator. """ diff1, diff2 = self.__diffGenerator.getResult()[:2] - + if diff1: self.lDiffParser = GitDiffParser(diff1) for line in diff1[:]: @@ -1101,7 +1139,7 @@ self.lDiffEdit.setPlainText("".join(diff1)) else: self.lDiffParser = None - + if diff2: self.rDiffParser = GitDiffParser(diff2) for line in diff2[:]: @@ -1112,17 +1150,17 @@ self.rDiffEdit.setPlainText("".join(diff2)) else: self.rDiffParser = None - + for diffEdit in [self.lDiffEdit, self.rDiffEdit]: tc = diffEdit.textCursor() tc.movePosition(QTextCursor.MoveOperation.Start) diffEdit.setTextCursor(tc) diffEdit.ensureCursorVisible() - + def __showLDiffContextMenu(self, coord): """ Private slot to show the context menu of the status list. - + @param coord position of the mouse pointer (QPoint) """ if bool(self.lDiffEdit.toPlainText()): @@ -1137,16 +1175,16 @@ self.__revertLinesAct.setEnabled(False) self.__stageHunkAct.setEnabled(True) self.__revertHunkAct.setEnabled(True) - + cursor = self.lDiffEdit.cursorForPosition(coord) self.lDiffEdit.setTextCursor(cursor) - + self.__lDiffMenu.popup(self.lDiffEdit.mapToGlobal(coord)) - + def __showRDiffContextMenu(self, coord): """ Private slot to show the context menu of the status list. - + @param coord position of the mouse pointer (QPoint) """ if bool(self.rDiffEdit.toPlainText()): @@ -1157,12 +1195,12 @@ else: self.__unstageLinesAct.setEnabled(False) self.__unstageHunkAct.setEnabled(True) - + cursor = self.rDiffEdit.cursorForPosition(coord) self.rDiffEdit.setTextCursor(cursor) - + self.__rDiffMenu.popup(self.rDiffEdit.mapToGlobal(coord)) - + def __stageHunkOrLines(self): """ Private method to stage the selected lines or hunk. @@ -1171,20 +1209,19 @@ startIndex, endIndex = self.__selectedLinesIndexes(self.lDiffEdit) patch = ( self.lDiffParser.createLinesPatch(startIndex, endIndex) - if cursor.hasSelection() else - self.lDiffParser.createHunkPatch(startIndex) + if cursor.hasSelection() + else self.lDiffParser.createHunkPatch(startIndex) ) if patch: patchFile = self.__tmpPatchFileName() try: with open(patchFile, "w") as f: f.write(patch) - self.vcs.gitApply(self.dname, patchFile, cached=True, - noDialog=True) + self.vcs.gitApply(self.dname, patchFile, cached=True, noDialog=True) self.on_refreshButton_clicked() finally: os.remove(patchFile) - + def __unstageHunkOrLines(self): """ Private method to unstage the selected lines or hunk. @@ -1192,22 +1229,22 @@ cursor = self.rDiffEdit.textCursor() startIndex, endIndex = self.__selectedLinesIndexes(self.rDiffEdit) patch = ( - self.rDiffParser.createLinesPatch(startIndex, endIndex, - reverse=True) - if cursor.hasSelection() else - self.rDiffParser.createHunkPatch(startIndex) + self.rDiffParser.createLinesPatch(startIndex, endIndex, reverse=True) + if cursor.hasSelection() + else self.rDiffParser.createHunkPatch(startIndex) ) if patch: patchFile = self.__tmpPatchFileName() try: with open(patchFile, "w") as f: f.write(patch) - self.vcs.gitApply(self.dname, patchFile, cached=True, - reverse=True, noDialog=True) + self.vcs.gitApply( + self.dname, patchFile, cached=True, reverse=True, noDialog=True + ) self.on_refreshButton_clicked() finally: os.remove(patchFile) - + def __revertHunkOrLines(self): """ Private method to revert the selected lines or hunk. @@ -1216,18 +1253,19 @@ startIndex, endIndex = self.__selectedLinesIndexes(self.lDiffEdit) title = ( self.tr("Revert selected lines") - if cursor.hasSelection() else - self.tr("Revert hunk") + if cursor.hasSelection() + else self.tr("Revert hunk") ) res = EricMessageBox.yesNo( self, title, - self.tr("""Are you sure you want to revert the selected""" - """ changes?""")) + self.tr("""Are you sure you want to revert the selected""" """ changes?"""), + ) if res: if cursor.hasSelection(): - patch = self.lDiffParser.createLinesPatch(startIndex, endIndex, - reverse=True) + patch = self.lDiffParser.createLinesPatch( + startIndex, endIndex, reverse=True + ) else: patch = self.lDiffParser.createHunkPatch(startIndex) if patch: @@ -1235,16 +1273,17 @@ try: with open(patchFile, "w") as f: f.write(patch) - self.vcs.gitApply(self.dname, patchFile, reverse=True, - noDialog=True) + self.vcs.gitApply( + self.dname, patchFile, reverse=True, noDialog=True + ) self.on_refreshButton_clicked() finally: os.remove(patchFile) - + def __selectedLinesIndexes(self, diffEdit): """ Private method to extract the indexes of the selected lines. - + @param diffEdit reference to the edit widget (QTextEdit) @return tuple of start and end indexes (integer, integer) """ @@ -1253,7 +1292,7 @@ selectionEnd = cursor.selectionEnd() startIndex = -1 - + lineStart = 0 for lineIdx, line in enumerate(diffEdit.toPlainText().splitlines()): lineEnd = lineStart + len(line) @@ -1265,19 +1304,19 @@ lineStart = lineEnd + 1 return startIndex, endIndex - + def __tmpPatchFileName(self): """ Private method to generate a temporary patch file. - + @return name of the temporary file (string) """ - prefix = 'eric-git-{0}-'.format(os.getpid()) - suffix = '-patch' + prefix = "eric-git-{0}-".format(os.getpid()) + suffix = "-patch" fd, path = tempfile.mkstemp(suffix, prefix) os.close(fd) return path - + def __refreshDiff(self): """ Private method to refresh the diff output after a refresh. @@ -1288,5 +1327,5 @@ if itm.text(self.__pathColumn) == self.__selectedName: itm.setSelected(True) break - + self.__selectedName = ""