--- a/Plugins/VcsPlugins/vcsMercurial/HgLogDialog.py Fri Jan 27 19:18:52 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,551 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2010 - 2017 Detlev Offenbach <detlev@die-offenbachs.de> -# - -""" -Module implementing a dialog to show the output of the hg log command process. -""" - -from __future__ import unicode_literals -try: - str = unicode -except NameError: - pass - -import os - -from PyQt5.QtCore import pyqtSlot, QProcess, QTimer, QUrl, QByteArray, \ - qVersion -from PyQt5.QtGui import QTextCursor -from PyQt5.QtWidgets import QWidget, QDialogButtonBox, QApplication, QLineEdit - -from E5Gui.E5Application import e5App -from E5Gui import E5MessageBox - -from .Ui_HgLogDialog import Ui_HgLogDialog - -import Utilities - - -class HgLogDialog(QWidget, Ui_HgLogDialog): - """ - Class implementing a dialog to show the output of the hg log command - process. - - The dialog is nonmodal. Clicking a link in the upper text pane shows - a diff of the revisions. - """ - def __init__(self, vcs, mode="log", bundle=None, isFile=False, - parent=None): - """ - Constructor - - @param vcs reference to the vcs object - @param mode mode of the dialog (string; one of log, incoming, outgoing) - @param bundle name of a bundle file (string) - @param isFile flag indicating log for a file is to be shown (boolean) - @param parent parent widget (QWidget) - """ - super(HgLogDialog, self).__init__(parent) - self.setupUi(self) - - self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) - - self.process = QProcess() - self.vcs = vcs - if mode in ("log", "incoming", "outgoing"): - self.mode = mode - else: - self.mode = "log" - self.bundle = bundle - self.__hgClient = self.vcs.getClient() - - self.contents.setHtml( - self.tr('<b>Processing your request, please wait...</b>')) - - self.process.finished.connect(self.__procFinished) - self.process.readyReadStandardOutput.connect(self.__readStdout) - self.process.readyReadStandardError.connect(self.__readStderr) - - self.contents.anchorClicked.connect(self.__sourceChanged) - - self.revisions = [] # stack of remembered revisions - self.revString = self.tr('Revision') - self.projectMode = False - - self.logEntries = [] # list of log entries - self.lastLogEntry = {} - self.fileCopies = {} - self.endInitialText = False - self.initialText = [] - - self.diff = None - - self.sbsCheckBox.setEnabled(isFile) - self.sbsCheckBox.setVisible(isFile) - - def closeEvent(self, e): - """ - Protected slot implementing a close event handler. - - @param e close event (QCloseEvent) - """ - if self.__hgClient: - if self.__hgClient.isExecuting(): - self.__hgClient.cancel() - else: - 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, fn, noEntries=0, revisions=None): - """ - Public slot to start the hg log command. - - @param fn filename to show the log for (string) - @param noEntries number of entries to show (integer) - @param revisions revisions to show log for (list of strings) - """ - self.errorGroup.hide() - QApplication.processEvents() - - self.intercept = False - 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.activateWindow() - self.raise_() - - preargs = [] - args = self.vcs.initCommand(self.mode) - if noEntries and self.mode == "log": - args.append('--limit') - args.append(str(noEntries)) - if self.mode in ("incoming", "outgoing"): - args.append("--newest-first") - if self.vcs.hasSubrepositories(): - args.append("--subrepos") - if self.mode == "log": - args.append('--copies') - args.append('--template') - args.append(os.path.join(os.path.dirname(__file__), - "templates", - "logDialogBookmarkPhase.tmpl")) - if self.mode == "incoming": - if self.bundle: - args.append(self.bundle) - elif not self.vcs.hasSubrepositories(): - project = e5App().getObject("Project") - self.vcs.bundleFile = os.path.join( - project.getProjectManagementDir(), "hg-bundle.hg") - if os.path.exists(self.vcs.bundleFile): - os.remove(self.vcs.bundleFile) - preargs = args[:] - preargs.append("--quiet") - preargs.append('--bundle') - preargs.append(self.vcs.bundleFile) - args.append(self.vcs.bundleFile) - if revisions: - for rev in revisions: - args.append("--rev") - args.append(rev) - if not self.projectMode: - args.append(self.filename) - - if self.__hgClient: - self.inputGroup.setEnabled(False) - self.inputGroup.hide() - - if preargs: - out, err = self.__hgClient.runcommand(preargs) - else: - err = "" - if err: - self.__showError(err) - elif self.mode != "incoming" or \ - (self.vcs.bundleFile and - os.path.exists(self.vcs.bundleFile)) or \ - self.bundle: - out, err = self.__hgClient.runcommand(args) - if err: - self.__showError(err) - if out and self.isVisible(): - for line in out.splitlines(True): - self.__processOutputLine(line) - if self.__hgClient.wasCanceled(): - break - self.__finish() - else: - self.process.kill() - - self.process.setWorkingDirectory(self.repodir) - - if preargs: - process = QProcess() - process.setWorkingDirectory(self.repodir) - process.start('hg', args) - procStarted = process.waitForStarted(5000) - if procStarted: - process.waitForFinished(30000) - - if self.mode != "incoming" or \ - (self.vcs.bundleFile and - os.path.exists(self.vcs.bundleFile)) or \ - self.bundle: - self.process.start('hg', 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('hg')) - else: - self.__finish() - - def __getParents(self, rev): - """ - Private method to get the parents of the currently viewed - file/directory. - - @param rev revision number to get parents for (string) - @return list of parent revisions (list of strings) - """ - errMsg = "" - parents = [] - - if int(rev) > 0: - args = self.vcs.initCommand("parents") - if self.mode == "incoming": - if self.bundle: - args.append("--repository") - args.append(self.bundle) - elif self.vcs.bundleFile and \ - os.path.exists(self.vcs.bundleFile): - args.append("--repository") - args.append(self.vcs.bundleFile) - args.append("--template") - args.append("{rev}:{node|short}\n") - args.append("-r") - args.append(rev) - if not self.projectMode: - args.append(self.filename) - - output = "" - if self.__hgClient: - output, errMsg = self.__hgClient.runcommand(args) - else: - process = QProcess() - process.setWorkingDirectory(self.repodir) - process.start('hg', args) - procStarted = process.waitForStarted(5000) - if procStarted: - finished = process.waitForFinished(30000) - if finished and process.exitCode() == 0: - output = str(process.readAllStandardOutput(), - self.vcs.getEncoding(), 'replace') - else: - if not finished: - errMsg = self.tr( - "The hg process did not finish within 30s.") - else: - errMsg = self.tr("Could not start the hg executable.") - - if errMsg: - E5MessageBox.critical( - self, - self.tr("Mercurial Error"), - errMsg) - - if output: - parents = [p for p in output.strip().splitlines()] - - return parents - - 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 __finish(self): - """ - Private slot called when the process finished or the user pressed - the button. - """ - self.inputGroup.setEnabled(False) - self.inputGroup.hide() - - self.contents.clear() - - if not self.logEntries: - self.errors.append(self.tr("No log available for '{0}'") - .format(self.filename)) - self.errorGroup.show() - return - - html = "" - - if self.initialText: - for line in self.initialText: - html += Utilities.html_encode(line.strip()) - html += '<br />\n' - html += '{0}<br/>\n'.format(80 * "=") - - for entry in self.logEntries: - fileCopies = {} - if entry["file_copies"]: - for fentry in entry["file_copies"].split(", "): - newName, oldName = fentry[:-1].split(" (") - fileCopies[newName] = oldName - - rev, hexRev = entry["change"].split(":") - dstr = '<p><b>{0} {1}</b>'.format(self.revString, entry["change"]) - if entry["parents"]: - parents = entry["parents"].split() - else: - parents = self.__getParents(rev) - for parent in parents: - url = QUrl() - url.setScheme("file") - url.setPath(self.filename) - if qVersion() >= "5.0.0": - query = parent.split(":")[0] + '_' + rev - url.setQuery(query) - else: - query = QByteArray() - query.append(parent.split(":")[0]).append('_').append(rev) - url.setEncodedQuery(query) - dstr += ' [<a href="{0}" name="{1}" id="{1}">{2}</a>]'.format( - url.toString(), query, - self.tr('diff to {0}').format(parent), - ) - dstr += '<br />\n' - html += dstr - - if "phase" in entry: - html += self.tr("Phase: {0}<br />\n")\ - .format(entry["phase"]) - - html += self.tr("Branch: {0}<br />\n")\ - .format(entry["branches"]) - - html += self.tr("Tags: {0}<br />\n").format(entry["tags"]) - - if "bookmarks" in entry: - html += self.tr("Bookmarks: {0}<br />\n")\ - .format(entry["bookmarks"]) - - html += self.tr("Parents: {0}<br />\n")\ - .format(entry["parents"]) - - html += self.tr('<i>Author: {0}</i><br />\n')\ - .format(Utilities.html_encode(entry["user"])) - - date, time = entry["date"].split()[:2] - html += self.tr('<i>Date: {0}, {1}</i><br />\n')\ - .format(date, time) - - for line in entry["description"]: - html += Utilities.html_encode(line.strip()) - html += '<br />\n' - - if entry["file_adds"]: - html += '<br />\n' - for f in entry["file_adds"].strip().split(", "): - if f in fileCopies: - html += self.tr( - 'Added {0} (copied from {1})<br />\n')\ - .format(Utilities.html_encode(f), - Utilities.html_encode(fileCopies[f])) - else: - html += self.tr('Added {0}<br />\n')\ - .format(Utilities.html_encode(f)) - - if entry["files_mods"]: - html += '<br />\n' - for f in entry["files_mods"].strip().split(", "): - html += self.tr('Modified {0}<br />\n')\ - .format(Utilities.html_encode(f)) - - if entry["file_dels"]: - html += '<br />\n' - for f in entry["file_dels"].strip().split(", "): - html += self.tr('Deleted {0}<br />\n')\ - .format(Utilities.html_encode(f)) - - html += '</p>{0}<br/>\n'.format(60 * "=") - - self.contents.setHtml(html) - tc = self.contents.textCursor() - tc.movePosition(QTextCursor.Start) - self.contents.setTextCursor(tc) - self.contents.ensureCursorVisible() - - 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.StandardOutput) - - while self.process.canReadLine(): - s = str(self.process.readLine(), self.vcs.getEncoding(), '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 line == "@@@\n": - self.logEntries.append(self.lastLogEntry) - self.lastLogEntry = {} - self.fileCopies = {} - else: - try: - key, value = line.split("|", 1) - except ValueError: - key = "" - value = line - if key == "change": - self.endInitialText = True - if key in ("change", "tags", "parents", "user", "date", - "file_copies", "file_adds", "files_mods", "file_dels", - "bookmarks", "phase"): - self.lastLogEntry[key] = value.strip() - elif key == "branches": - if value.strip(): - self.lastLogEntry[key] = value.strip() - else: - self.lastLogEntry[key] = "default" - elif key == "description": - self.lastLogEntry[key] = [value.strip()] - else: - if self.endInitialText: - self.lastLogEntry["description"].append(value.strip()) - else: - self.initialText.append(value) - - 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.vcs.getEncoding(), '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 __sourceChanged(self, url): - """ - Private slot to handle the sourceChanged signal of the contents pane. - - @param url the url that was clicked (QUrl) - """ - filename = url.path() - if Utilities.isWindowsPlatform(): - if filename.startswith("/"): - filename = filename[1:] - if qVersion() >= "5.0.0": - ver = url.query() - else: - ver = bytes(url.encodedQuery()).decode() - v1, v2 = ver.split('_') - if v1 == "" or v2 == "": - return - self.contents.scrollToAnchor(ver) - - if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked(): - self.vcs.hgSbsDiff(filename, revisions=(v1, v2)) - else: - if self.diff is None: - from .HgDiffDialog import HgDiffDialog - self.diff = HgDiffDialog(self.vcs) - self.diff.show() - self.diff.start(filename, [v1, v2], self.bundle) - - 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 hg process. - """ - input = self.input.text() - input += os.linesep - - if self.passwordCheckBox.isChecked(): - self.errors.insertPlainText(os.linesep) - self.errors.ensureCursorVisible() - else: - self.errors.insertPlainText(input) - self.errors.ensureCursorVisible() - - self.process.write(input) - - 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(HgLogDialog, self).keyPressEvent(evt)