diff -r 000000000000 -r de9c2efb9d02 Plugins/VcsPlugins/vcsPySvn/SvnDiffDialog.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/VcsPlugins/vcsPySvn/SvnDiffDialog.py Mon Dec 28 16:03:33 2009 +0000 @@ -0,0 +1,365 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2003 - 2009 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to show the output of the svn diff command process. +""" + +import os +import sys +import types + +import pysvn + +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +from E4Gui.E4Application import e4App + +from SvnDialogMixin import SvnDialogMixin +from Ui_SvnDiffDialog import Ui_SvnDiffDialog + +import Utilities + +class SvnDiffDialog(QWidget, SvnDialogMixin, Ui_SvnDiffDialog): + """ + Class implementing a dialog to show the output of the svn diff command. + """ + def __init__(self, vcs, parent = None): + """ + Constructor + + @param vcs reference to the vcs object + @param parent parent widget (QWidget) + """ + QWidget.__init__(self, parent) + self.setupUi(self) + SvnDialogMixin.__init__(self) + + self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) + + self.vcs = vcs + + if Utilities.isWindowsPlatform(): + self.contents.setFontFamily("Lucida Console") + else: + self.contents.setFontFamily("Monospace") + + self.cNormalFormat = self.contents.currentCharFormat() + self.cAddedFormat = self.contents.currentCharFormat() + self.cAddedFormat.setBackground(QBrush(QColor(190, 237, 190))) + self.cRemovedFormat = self.contents.currentCharFormat() + self.cRemovedFormat.setBackground(QBrush(QColor(237, 190, 190))) + self.cLineNoFormat = self.contents.currentCharFormat() + self.cLineNoFormat.setBackground(QBrush(QColor(255, 220, 168))) + + 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 __getVersionArg(self, version): + """ + Private method to get a pysvn revision object for the given version number. + + @param version revision (integer or string) + @return revision object (pysvn.Revision) + """ + if type(version) == type(1) or type(version) == type(1L): + return pysvn.Revision(pysvn.opt_revision_kind.number, version) + elif version.startswith("{"): + dateStr = version[1:-1] + secs = QDateTime.fromString(dateStr, Qt.ISODate).toTime_t() + return pysvn.Revision(pysvn.opt_revision_kind.date, secs) + elif version == "HEAD": + return pysvn.Revision(pysvn.opt_revision_kind.head) + elif version == "COMMITTED": + return pysvn.Revision(pysvn.opt_revision_kind.committed) + elif version == "BASE": + return pysvn.Revision(pysvn.opt_revision_kind.base) + elif version == "WORKING": + return pysvn.Revision(pysvn.opt_revision_kind.working) + elif version == "PREV": + return pysvn.Revision(pysvn.opt_revision_kind.previous) + else: + return pysvn.Revision(pysvn.opt_revision_kind.unspecified) + + def __getDiffSummaryKind(self, summaryKind): + """ + Private method to get a string descripion of the diff summary. + + @param summaryKind (pysvn.diff_summarize.summarize_kind) + @return one letter string indicating the change type (string) + """ + if summaryKind == pysvn.diff_summarize_kind.delete: + return "D" + elif summaryKind == pysvn.diff_summarize_kind.modified: + return "M" + elif summaryKind == pysvn.diff_summarize_kind.added: + return "A" + elif summaryKind == pysvn.diff_summarize_kind.normal: + return "N" + else: + return " " + + def start(self, fn, versions = None, urls = None, summary = False, pegRev = None): + """ + Public slot to start the svn diff command. + + @param fn filename to be diffed (string) + @param versions list of versions to be diffed (list of up to 2 integer or None) + @keyparam urls list of repository URLs (list of 2 strings) + @keyparam summary flag indicating a summarizing diff + (only valid for URL diffs) (boolean) + @keyparam pegRev revision number the filename is valid (integer) + """ + self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) + + self._reset() + self.errorGroup.hide() + + QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + QApplication.processEvents() + self.filename = fn + + self.contents.clear() + self.paras = 0 + + if Utilities.hasEnvironmentEntry('TEMP'): + tmpdir = Utilities.getEnvironmentEntry('TEMP') + elif Utilities.hasEnvironmentEntry('TMPDIR'): + tmpdir = Utilities.getEnvironmentEntry('TMPDIR') + elif Utilities.hasEnvironmentEntry('TMP'): + tmpdir = Utilities.getEnvironmentEntry('TMP') + elif os.path.exists('/var/tmp'): + tmpdir = '/var/tmp' + elif os.path.exists('/usr/tmp'): + tmpdir = '/usr/tmp' + elif os.path.exists('/tmp'): + tmpdir = '/tmp' + else: + QMessageBox.critical(self, + self.trUtf8("Subversion Diff"), + self.trUtf8("""There is no temporary directory available.""")) + return + + tmpdir = os.path.join(tmpdir, 'svn_tmp') + if not os.path.exists(tmpdir): + os.mkdir(tmpdir) + + opts = self.vcs.options['global'] + self.vcs.options['diff'] + recurse = "--non-recursive" not in opts + + if versions is not None: + self.raise_() + self.activateWindow() + rev1 = self.__getVersionArg(versions[0]) + if len(versions) == 1: + rev2 = self.__getVersionArg("WORKING") + else: + rev2 = self.__getVersionArg(versions[1]) + else: + rev1 = self.__getVersionArg("BASE") + rev2 = self.__getVersionArg("WORKING") + + if urls is not None: + rev1 = self.__getVersionArg("HEAD") + rev2 = self.__getVersionArg("HEAD") + + if type(fn) is types.ListType: + dname, fnames = self.vcs.splitPathList(fn) + else: + dname, fname = self.vcs.splitPath(fn) + fnames = [fname] + + locker = QMutexLocker(self.vcs.vcsExecutionMutex) + cwd = os.getcwd() + os.chdir(dname) + try: + ppath = e4App().getObject('Project').getProjectPath() + dname = dname.replace(ppath, '') + if dname: + dname += "/" + for name in fnames: + self.__showError(self.trUtf8("Processing file '{0}'...\n").format(name)) + if urls is not None: + url1 = "%s/%s%s" % (urls[0], dname, name) + url2 = "%s/%s%s" % (urls[1], dname, name) + if summary: + diff_summary = self.client.diff_summarize(\ + url1, revision1 = rev1, + url_or_path2 = url2, revision2 = rev2, + recurse = recurse) + diff_list = [] + for diff_sum in diff_summary: + diff_list.append("%s %s" % \ + (self.__getDiffSummaryKind(diff_sum['summarize_kind']), + diff_sum['path'])) + diffText = os.linesep.join(diff_list) + else: + diffText = self.client.diff(tmpdir, + url1, revision1 = rev1, + url_or_path2 = url2, revision2 = rev2, + recurse = recurse) + else: + if pegRev is not None: + diffText = self.client.diff_peg(tmpdir, name, + peg_revision = self.__getVersionArg(pegRev), + revision_start = rev1, revision_end = rev2, recurse = recurse) + else: + diffText = self.client.diff(tmpdir, name, + revision1 = rev1, revision2 = rev2, recurse = recurse) + counter = 0 + for line in diffText.splitlines(): + self.__appendText("%s%s" % (line, os.linesep)) + counter += 1 + if counter == 30: + # check for cancel every 30 lines + counter = 0 + if self._clientCancelCallback(): + break + if self._clientCancelCallback(): + break + except pysvn.ClientError, e: + self.__showError(e.args[0]) + locker.unlock() + os.chdir(cwd) + self.__finish() + + if self.paras == 0: + self.contents.insertPlainText(\ + self.trUtf8('There is no difference.')) + return + + self.buttonBox.button(QDialogButtonBox.Save).setEnabled(True) + + def __appendText(self, line): + """ + Private method to append text to the end of the contents pane. + + @param line line of text to insert (string) + """ + if line.startswith('+') or line.startswith('>') or line.startswith('A '): + format = self.cAddedFormat + elif line.startswith('-') or line.startswith('<') or line.startswith('D '): + format = self.cRemovedFormat + elif line.startswith('@@'): + format = self.cLineNoFormat + else: + format = self.cNormalFormat + + tc = self.contents.textCursor() + tc.movePosition(QTextCursor.End) + self.contents.setTextCursor(tc) + self.contents.setCurrentCharFormat(format) + self.contents.insertPlainText(line) + self.paras += 1 + + def __finish(self): + """ + Private slot called when the user pressed the button. + """ + QApplication.restoreOverrideCursor() + + self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) + 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() + elif button == self.buttonBox.button(QDialogButtonBox.Save): + self.on_saveButton_clicked() + + @pyqtSlot() + def on_saveButton_clicked(self): + """ + Private slot to handle the Save button press. + + It saves the diff shown in the dialog to a file in the local + filesystem. + """ + if type(self.filename) is types.ListType: + if len(self.filename) > 1: + fname = self.vcs.splitPathList(self.filename)[0] + else: + dname, fname = self.vcs.splitPath(self.filename[0]) + if fname != '.': + fname = "%s.diff" % self.filename[0] + else: + fname = dname + else: + fname = self.vcs.splitPath(self.filename)[0] + + fname, selectedFilter = QFileDialog.getSaveFileNameAndFilter(\ + self, + self.trUtf8("Save Diff"), + fname, + self.trUtf8("Patch Files (*.diff)"), + None, + QFileDialog.Options(QFileDialog.DontConfirmOverwrite)) + + if not fname: + return # user aborted + + ext = QFileInfo(fname).suffix() + if not ext: + ex = selectedFilter.split("(*")[1].split(")")[0] + if ex: + fname += ex + if QFileInfo(fname).exists(): + res = QMessageBox.warning(self, + self.trUtf8("Save Diff"), + self.trUtf8("<p>The patch file <b>{0}</b> already exists.</p>") + .format(fname), + QMessageBox.StandardButtons(\ + QMessageBox.Abort | \ + QMessageBox.Save), + QMessageBox.Abort) + if res != QMessageBox.Save: + return + fname = Utilities.toNativeSeparators(fname) + + try: + f = open(fname, "wb") + f.write(self.contents.toPlainText()) + f.close() + except IOError, why: + QMessageBox.critical(self, self.trUtf8('Save Diff'), + self.trUtf8('<p>The patch file <b>{0}</b> could not be saved.' + '<br>Reason: {1}</p>') + .format(fname, unicode(why))) + + 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()