Wed, 02 Jan 2013 10:31:48 +0100
Updated copyright for 2013.
# -*- 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. """ import os 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 from .SvnDiffDialog import SvnDiffDialog 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, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super().__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.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) dstr = self.trUtf8('<i>author: {0}</i><br />\n').format(log["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 log["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']: dstr = '{0} {1}'\ .format(self.flags[changeInfo["action"]], changeInfo["path"]) if changeInfo["copyfrom_path"] is not None: dstr += self.trUtf8(" (copied from {0}, revision {1})")\ .format(changeInfo["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.diff is None: 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()