Plugins/VcsPlugins/vcsPySvn/SvnLogDialog.py

Mon, 05 Aug 2013 15:58:30 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Mon, 05 Aug 2013 15:58:30 +0200
changeset 2845
7e55c6268c35
parent 2406
975af671146d
child 2847
1843ef6e2656
child 2848
15c84f1f745a
permissions
-rw-r--r--

Added capability to compare files side-by-side to the Subversion interfaces.

# -*- 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

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().__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.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.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()

eric ide

mercurial