Wed, 30 Dec 2020 11:00:44 +0100
Updated copyright for 2021.
# -*- coding: utf-8 -*- # Copyright (c) 2014 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing a dialog to show a list of tags or branches. """ import os from PyQt5.QtCore import pyqtSlot, QProcess, Qt, QTimer, QCoreApplication from PyQt5.QtWidgets import ( QDialog, QDialogButtonBox, QHeaderView, QTreeWidgetItem, QLineEdit ) from E5Gui import E5MessageBox from .Ui_GitTagBranchListDialog import Ui_GitTagBranchListDialog import Preferences from Globals import strToQByteArray class GitTagBranchListDialog(QDialog, Ui_GitTagBranchListDialog): """ Class implementing a dialog to show a list of tags or branches. """ def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(GitTagBranchListDialog, self).__init__(parent) self.setupUi(self) self.setWindowFlags(Qt.Window) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.process = QProcess() self.vcs = vcs self.tagList.headerItem().setText(self.tagList.columnCount(), "") self.tagList.header().setSortIndicator(1, Qt.AscendingOrder) self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) self.show() QCoreApplication.processEvents() 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.NotRunning ): self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) e.accept() def start(self, path, tags, listAll=True, merged=True): """ Public slot to start the tag/branch list command. @param path name of directory to be listed (string) @param tags flag indicating a list of tags is requested (False = branches, True = tags) @param listAll flag indicating to show all tags or branches (boolean) @param merged flag indicating to show only merged or non-merged branches (boolean) """ self.tagList.clear() self.errorGroup.hide() self.intercept = False self.tagsMode = tags if tags: self.tagList.setHeaderItem( QTreeWidgetItem([self.tr("Commit"), self.tr("Name"), self.tr("Annotation Message")])) else: self.setWindowTitle(self.tr("Git Branches List")) self.tagList.setHeaderItem( QTreeWidgetItem([self.tr("Commit"), self.tr("Name")])) self.activateWindow() dname, fname = self.vcs.splitPath(path) # find the root of the repo self.repodir = 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 if self.tagsMode: args = self.vcs.initCommand("tag") args.append('--list') args.append('-n') else: args = self.vcs.initCommand("branch") args.append('--list') args.append('--all') args.append('--verbose') if not listAll: if merged: args.append("--merged") else: args.append("--no-merged") self.process.kill() self.process.setWorkingDirectory(self.repodir) self.process.start('git', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.' ).format('git')) else: self.inputGroup.setEnabled(True) self.inputGroup.show() 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.NotRunning ): self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.inputGroup.setEnabled(False) self.inputGroup.hide() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.buttonBox.button(QDialogButtonBox.Close).setFocus( Qt.OtherFocusReason) self.__resizeColumns() self.__resort() 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.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.__finish() 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 __resort(self): """ Private method to resort the tree. """ self.tagList.sortItems( self.tagList.sortColumn(), self.tagList.header().sortIndicatorOrder()) def __resizeColumns(self): """ Private method to resize the list columns. """ self.tagList.header().resizeSections(QHeaderView.ResizeToContents) self.tagList.header().setStretchLastSection(True) def __generateItem(self, commit, name, msg="", bold=False, italic=False): """ Private method to generate a tag item in the tag list. @param commit commit id of the tag/branch (string) @param name name of the tag/branch (string) @param msg tag annotation message @param bold flag indicating to show the entry in bold (boolean) @param italic flag indicating to show the entry in italic (boolean) """ itm = QTreeWidgetItem(self.tagList) itm.setData(0, Qt.DisplayRole, commit) itm.setData(1, Qt.DisplayRole, name) if msg: itm.setData(2, Qt.DisplayRole, msg) itm.setTextAlignment(0, Qt.AlignRight) if bold or italic: font = itm.font(1) if bold: font.setBold(True) if italic: font.setItalic(True) itm.setFont(1, font) def __getCommit(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("--no-patch") args.append(tag) output = "" process = QProcess() process.setWorkingDirectory(self.repodir) 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') if output: for line in output.splitlines(): if line.startswith("commit "): commitId = line.split()[1] return commitId return "" def __readStdout(self): """ Private slot to handle the readyReadStdout signal. It reads the output of the process, formats it and inserts it into the contents pane. """ self.process.setReadChannel(QProcess.StandardOutput) while self.process.canReadLine(): s = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') self.__processOutputLine(s) def __processOutputLine(self, line): """ Private method to process the lines of output. @param line output line to be processed (string) """ if self.tagsMode: name, msg = line.strip().split(None, 1) commit = self.__getCommit(name) self.__generateItem(commit, name, msg.strip()) else: bold = line.startswith("* ") line = line[2:] if line.startswith("("): name, line = line[1:].split(")", 1) commit = line.strip().split(None, 1)[0] else: data = line.split(None, 2) if data[1].startswith("->"): name = " ".join(line.strip().split()) commit = "" else: commit = data[1] name = data[0] italic = name.startswith("remotes/") self.__generateItem(commit, name, bold=bold, italic=italic) def __readStderr(self): """ Private slot to handle the readyReadStderr 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') 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() 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.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the git process. """ 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: self.intercept = False evt.accept() return super(GitTagBranchListDialog, self).keyPressEvent(evt)