Sat, 14 Dec 2013 23:44:25 +0100
# __IGNORE_WARNING__ added/ removed.
# -*- coding: utf-8 -*- # Copyright (c) 2003 - 2013 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing a dialog to show the output of the svn log command process. """ from __future__ import unicode_literals import os import sys import pysvn from PyQt4.QtCore import QMutexLocker, QByteArray, QUrl, Qt from PyQt4.QtGui import QWidget, QCursor, QApplication, QTextCursor, \ QDialogButtonBox from .SvnUtilities import formatTime from .SvnDialogMixin import SvnDialogMixin from .Ui_SvnLogDialog import Ui_SvnLogDialog import Utilities class SvnLogDialog(QWidget, SvnDialogMixin, Ui_SvnLogDialog): """ Class implementing a dialog to show the output of the svn log command. The dialog is nonmodal. Clicking a link in the upper text pane shows a diff of the versions. """ def __init__(self, vcs, isFile=False, parent=None): """ Constructor @param vcs reference to the vcs object @param isFile flag indicating log for a file is to be shown (boolean) @param parent parent widget (QWidget) """ super(SvnLogDialog, self).__init__(parent) self.setupUi(self) SvnDialogMixin.__init__(self) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.vcs = vcs self.contents.setHtml( self.trUtf8('<b>Processing your request, please wait...</b>')) self.contents.anchorClicked.connect(self.__sourceChanged) self.flags = { 'A': self.trUtf8('Added'), 'D': self.trUtf8('Deleted'), 'M': self.trUtf8('Modified') } self.revString = self.trUtf8('revision') self.diff = None self.sbsCheckBox.setEnabled(isFile) self.sbsCheckBox.setVisible(isFile) self.client = self.vcs.getClient() self.client.callback_cancel = \ self._clientCancelCallback self.client.callback_get_login = \ self._clientLoginCallback self.client.callback_ssl_server_trust_prompt = \ self._clientSslServerTrustPromptCallback def start(self, fn, noEntries=0): """ Public slot to start the svn log command. @param fn filename to show the log for (string) @param noEntries number of entries to show (integer) """ self.errorGroup.hide() fetchLimit = 10 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() self.filename = fn dname, fname = self.vcs.splitPath(fn) opts = self.vcs.options['global'] + self.vcs.options['log'] verbose = "--verbose" in opts self.activateWindow() self.raise_() locker = QMutexLocker(self.vcs.vcsExecutionMutex) cwd = os.getcwd() os.chdir(dname) try: nextRev = 0 fetched = 0 logs = [] limit = noEntries or 9999999 while fetched < limit: flimit = min(fetchLimit, limit - fetched) if fetched == 0: revstart = pysvn.Revision(pysvn.opt_revision_kind.head) else: revstart = pysvn.Revision( pysvn.opt_revision_kind.number, nextRev) allLogs = self.client.log(fname, revision_start=revstart, discover_changed_paths=verbose, limit=flimit + 1, strict_node_history=False) if len(allLogs) <= flimit or self._clientCancelCallback(): logs.extend(allLogs) break else: logs.extend(allLogs[:-1]) nextRev = allLogs[-1]["revision"].number fetched += fetchLimit locker.unlock() self.contents.clear() self.__pegRev = None for log in logs: ver = "{0:d}".format(log["revision"].number) dstr = '<b>{0} {1}</b>'.format(self.revString, ver) if self.__pegRev is None: self.__pegRev = int(ver) try: lv = "{0:d}".format( logs[logs.index(log) + 1]["revision"].number) url = QUrl() url.setScheme("file") url.setPath(self.filename) query = QByteArray() query.append(lv).append('_').append(ver) url.setEncodedQuery(query) dstr += ' [<a href="{0}" name="{1}">{2}</a>]'.format( url.toString(), query, self.trUtf8('diff to {0}').format(lv) ) except IndexError: pass dstr += '<br />\n' self.contents.insertHtml(dstr) author = log["author"] message = log["message"] if sys.version_info[0] == 2: author = author.decode('utf-8') message = message.decode('utf-8') dstr = self.trUtf8('<i>author: {0}</i><br />\n')\ .format(author) self.contents.insertHtml(dstr) dstr = self.trUtf8('<i>date: {0}</i><br />\n')\ .format(formatTime(log["date"])) self.contents.insertHtml(dstr) self.contents.insertHtml('<br />\n') for line in message.splitlines(): self.contents.insertHtml(Utilities.html_encode(line)) self.contents.insertHtml('<br />\n') if len(log['changed_paths']) > 0: self.contents.insertHtml('<br />\n') for changeInfo in log['changed_paths']: action = changeInfo["action"] path = changeInfo["path"] if sys.version_info[0] == 2: action = action.decode('utf-8') path = path.decode('utf-8') dstr = '{0} {1}'.format(self.flags[action], path) if changeInfo["copyfrom_path"] is not None: copyfrom_path = changeInfo["copyfrom_path"] if sys.version_info[0] == 2: copyfrom_path = copyfrom_path.decode('utf-8') dstr += self.trUtf8( " (copied from {0}, revision {1})")\ .format(copyfrom_path, changeInfo["copyfrom_revision"].number) dstr += '<br />\n' self.contents.insertHtml(dstr) self.contents.insertHtml('<hr /><br />\n') except pysvn.ClientError as e: locker.unlock() self.__showError(e.args[0]) os.chdir(cwd) self.__finish() def __finish(self): """ Private slot called when the user pressed the button. """ QApplication.restoreOverrideCursor() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) tc = self.contents.textCursor() tc.movePosition(QTextCursor.Start) self.contents.setTextCursor(tc) self.contents.ensureCursorVisible() self._cancel() 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 __sourceChanged(self, url): """ Private slot to handle the sourceChanged signal of the contents pane. @param url the url that was clicked (QUrl) """ self.contents.setSource(QUrl('')) filename = url.path() if Utilities.isWindowsPlatform(): if filename.startswith("/"): filename = filename[1:] ver = bytes(url.encodedQuery()).decode() v1 = ver.split('_')[0] v2 = ver.split('_')[1] if not v1 or not v2: return try: v1 = int(v1) v2 = int(v2) except ValueError: return self.contents.scrollToAnchor(ver) if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked(): self.vcs.svnSbsDiff(filename, revisions=(v1, v2)) else: if self.diff is None: from .SvnDiffDialog import SvnDiffDialog self.diff = SvnDiffDialog(self.vcs) self.diff.show() self.diff.start(filename, [v1, v2], pegRev=self.__pegRev) def __showError(self, msg): """ Private slot to show an error message. @param msg error message to show (string) """ self.errorGroup.show() self.errors.insertPlainText(msg) self.errors.ensureCursorVisible()