PageRenderTime 27ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/eric4-4.4.19/eric/Plugins/VcsPlugins/vcsPySvn/SvnDiffDialog.py

#
Python | 367 lines | 309 code | 21 blank | 37 comment | 23 complexity | 7a06c768c498880ee140290f4ef54179 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, BSD-2-Clause
  1. # -*- coding: utf-8 -*-
  2. # Copyright (c) 2003 - 2011 Detlev Offenbach <detlev@die-offenbachs.de>
  3. #
  4. """
  5. Module implementing a dialog to show the output of the svn diff command process.
  6. """
  7. import os
  8. import sys
  9. import types
  10. import pysvn
  11. from PyQt4.QtCore import *
  12. from PyQt4.QtGui import *
  13. from KdeQt import KQFileDialog, KQMessageBox
  14. from KdeQt.KQApplication import e4App
  15. from SvnDialogMixin import SvnDialogMixin
  16. from Ui_SvnDiffDialog import Ui_SvnDiffDialog
  17. import Utilities
  18. class SvnDiffDialog(QWidget, SvnDialogMixin, Ui_SvnDiffDialog):
  19. """
  20. Class implementing a dialog to show the output of the svn diff command.
  21. """
  22. def __init__(self, vcs, parent = None):
  23. """
  24. Constructor
  25. @param vcs reference to the vcs object
  26. @param parent parent widget (QWidget)
  27. """
  28. QWidget.__init__(self, parent)
  29. self.setupUi(self)
  30. SvnDialogMixin.__init__(self)
  31. self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False)
  32. self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
  33. self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
  34. self.vcs = vcs
  35. if Utilities.isWindowsPlatform():
  36. self.contents.setFontFamily("Lucida Console")
  37. else:
  38. self.contents.setFontFamily("Monospace")
  39. self.cNormalFormat = self.contents.currentCharFormat()
  40. self.cAddedFormat = self.contents.currentCharFormat()
  41. self.cAddedFormat.setBackground(QBrush(QColor(190, 237, 190)))
  42. self.cRemovedFormat = self.contents.currentCharFormat()
  43. self.cRemovedFormat.setBackground(QBrush(QColor(237, 190, 190)))
  44. self.cLineNoFormat = self.contents.currentCharFormat()
  45. self.cLineNoFormat.setBackground(QBrush(QColor(255, 220, 168)))
  46. self.client = self.vcs.getClient()
  47. self.client.callback_cancel = \
  48. self._clientCancelCallback
  49. self.client.callback_get_login = \
  50. self._clientLoginCallback
  51. self.client.callback_ssl_server_trust_prompt = \
  52. self._clientSslServerTrustPromptCallback
  53. def __getVersionArg(self, version):
  54. """
  55. Private method to get a pysvn revision object for the given version number.
  56. @param version revision (integer or string)
  57. @return revision object (pysvn.Revision)
  58. """
  59. if type(version) == type(1) or type(version) == type(1L):
  60. return pysvn.Revision(pysvn.opt_revision_kind.number, version)
  61. elif version.startswith("{"):
  62. dateStr = version[1:-1]
  63. secs = QDateTime.fromString(dateStr, Qt.ISODate).toTime_t()
  64. return pysvn.Revision(pysvn.opt_revision_kind.date, secs)
  65. elif version == "HEAD":
  66. return pysvn.Revision(pysvn.opt_revision_kind.head)
  67. elif version == "COMMITTED":
  68. return pysvn.Revision(pysvn.opt_revision_kind.committed)
  69. elif version == "BASE":
  70. return pysvn.Revision(pysvn.opt_revision_kind.base)
  71. elif version == "WORKING":
  72. return pysvn.Revision(pysvn.opt_revision_kind.working)
  73. elif version == "PREV":
  74. return pysvn.Revision(pysvn.opt_revision_kind.previous)
  75. else:
  76. return pysvn.Revision(pysvn.opt_revision_kind.unspecified)
  77. def __getDiffSummaryKind(self, summaryKind):
  78. """
  79. Private method to get a string descripion of the diff summary.
  80. @param summaryKind (pysvn.diff_summarize.summarize_kind)
  81. @return one letter string indicating the change type (QString)
  82. """
  83. if summaryKind == pysvn.diff_summarize_kind.delete:
  84. return "D"
  85. elif summaryKind == pysvn.diff_summarize_kind.modified:
  86. return "M"
  87. elif summaryKind == pysvn.diff_summarize_kind.added:
  88. return "A"
  89. elif summaryKind == pysvn.diff_summarize_kind.normal:
  90. return "N"
  91. else:
  92. return " "
  93. def start(self, fn, versions = None, urls = None, summary = False, pegRev = None):
  94. """
  95. Public slot to start the svn diff command.
  96. @param fn filename to be diffed (string)
  97. @param versions list of versions to be diffed (list of up to 2 integer or None)
  98. @keyparam urls list of repository URLs (list of 2 strings)
  99. @keyparam summary flag indicating a summarizing diff
  100. (only valid for URL diffs) (boolean)
  101. @keyparam pegRev revision number the filename is valid (integer)
  102. """
  103. self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False)
  104. self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
  105. self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
  106. self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
  107. self._reset()
  108. self.errorGroup.hide()
  109. QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
  110. QApplication.processEvents()
  111. self.filename = fn
  112. self.contents.clear()
  113. self.paras = 0
  114. if Utilities.hasEnvironmentEntry('TEMP'):
  115. tmpdir = Utilities.getEnvironmentEntry('TEMP')
  116. elif Utilities.hasEnvironmentEntry('TMPDIR'):
  117. tmpdir = Utilities.getEnvironmentEntry('TMPDIR')
  118. elif Utilities.hasEnvironmentEntry('TMP'):
  119. tmpdir = Utilities.getEnvironmentEntry('TMP')
  120. elif os.path.exists('/var/tmp'):
  121. tmpdir = '/var/tmp'
  122. elif os.path.exists('/usr/tmp'):
  123. tmpdir = '/usr/tmp'
  124. elif os.path.exists('/tmp'):
  125. tmpdir = '/tmp'
  126. else:
  127. KQMessageBox.critical(self,
  128. self.trUtf8("Subversion Diff"),
  129. self.trUtf8("""There is no temporary directory available."""))
  130. return
  131. tmpdir = os.path.join(tmpdir, 'svn_tmp')
  132. if not os.path.exists(tmpdir):
  133. os.mkdir(tmpdir)
  134. opts = self.vcs.options['global'] + self.vcs.options['diff']
  135. recurse = "--non-recursive" not in opts
  136. if versions is not None:
  137. self.raise_()
  138. self.activateWindow()
  139. rev1 = self.__getVersionArg(versions[0])
  140. if len(versions) == 1:
  141. rev2 = self.__getVersionArg("WORKING")
  142. else:
  143. rev2 = self.__getVersionArg(versions[1])
  144. else:
  145. rev1 = self.__getVersionArg("BASE")
  146. rev2 = self.__getVersionArg("WORKING")
  147. if urls is not None:
  148. rev1 = self.__getVersionArg("HEAD")
  149. rev2 = self.__getVersionArg("HEAD")
  150. if type(fn) is types.ListType:
  151. dname, fnames = self.vcs.splitPathList(fn)
  152. else:
  153. dname, fname = self.vcs.splitPath(fn)
  154. fnames = [fname]
  155. locker = QMutexLocker(self.vcs.vcsExecutionMutex)
  156. cwd = os.getcwd()
  157. os.chdir(dname)
  158. try:
  159. ppath = e4App().getObject('Project').getProjectPath()
  160. dname = dname.replace(ppath, '')
  161. if dname:
  162. dname += "/"
  163. for name in fnames:
  164. self.__showError(self.trUtf8("Processing file '%1'...\n").arg(name))
  165. if urls is not None:
  166. url1 = "%s/%s%s" % (urls[0], dname, name)
  167. url2 = "%s/%s%s" % (urls[1], dname, name)
  168. if summary:
  169. diff_summary = self.client.diff_summarize(\
  170. url1, revision1 = rev1,
  171. url_or_path2 = url2, revision2 = rev2,
  172. recurse = recurse)
  173. diff_list = []
  174. for diff_sum in diff_summary:
  175. diff_list.append("%s %s" % \
  176. (self.__getDiffSummaryKind(diff_sum['summarize_kind']),
  177. diff_sum['path']))
  178. diffText = os.linesep.join(diff_list)
  179. else:
  180. diffText = self.client.diff(tmpdir,
  181. url1, revision1 = rev1,
  182. url_or_path2 = url2, revision2 = rev2,
  183. recurse = recurse)
  184. else:
  185. if pegRev is not None:
  186. diffText = self.client.diff_peg(tmpdir, name,
  187. peg_revision = self.__getVersionArg(pegRev),
  188. revision_start = rev1, revision_end = rev2, recurse = recurse)
  189. else:
  190. diffText = self.client.diff(tmpdir, name,
  191. revision1 = rev1, revision2 = rev2, recurse = recurse)
  192. counter = 0
  193. for line in diffText.splitlines():
  194. self.__appendText("%s%s" % (line, os.linesep))
  195. counter += 1
  196. if counter == 30:
  197. # check for cancel every 30 lines
  198. counter = 0
  199. if self._clientCancelCallback():
  200. break
  201. if self._clientCancelCallback():
  202. break
  203. except pysvn.ClientError, e:
  204. self.__showError(e.args[0])
  205. locker.unlock()
  206. os.chdir(cwd)
  207. self.__finish()
  208. if self.paras == 0:
  209. self.contents.insertPlainText(\
  210. self.trUtf8('There is no difference.'))
  211. return
  212. self.buttonBox.button(QDialogButtonBox.Save).setEnabled(True)
  213. def __appendText(self, line):
  214. """
  215. Private method to append text to the end of the contents pane.
  216. @param line line of text to insert (string)
  217. """
  218. if line.startswith('+') or line.startswith('>') or line.startswith('A '):
  219. format = self.cAddedFormat
  220. elif line.startswith('-') or line.startswith('<') or line.startswith('D '):
  221. format = self.cRemovedFormat
  222. elif line.startswith('@@'):
  223. format = self.cLineNoFormat
  224. else:
  225. format = self.cNormalFormat
  226. tc = self.contents.textCursor()
  227. tc.movePosition(QTextCursor.End)
  228. self.contents.setTextCursor(tc)
  229. self.contents.setCurrentCharFormat(format)
  230. self.contents.insertPlainText(line)
  231. self.paras += 1
  232. def __finish(self):
  233. """
  234. Private slot called when the user pressed the button.
  235. """
  236. QApplication.restoreOverrideCursor()
  237. self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
  238. self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
  239. self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
  240. tc = self.contents.textCursor()
  241. tc.movePosition(QTextCursor.Start)
  242. self.contents.setTextCursor(tc)
  243. self.contents.ensureCursorVisible()
  244. self._cancel()
  245. def on_buttonBox_clicked(self, button):
  246. """
  247. Private slot called by a button of the button box clicked.
  248. @param button button that was clicked (QAbstractButton)
  249. """
  250. if button == self.buttonBox.button(QDialogButtonBox.Close):
  251. self.close()
  252. elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
  253. self.__finish()
  254. elif button == self.buttonBox.button(QDialogButtonBox.Save):
  255. self.on_saveButton_clicked()
  256. @pyqtSignature("")
  257. def on_saveButton_clicked(self):
  258. """
  259. Private slot to handle the Save button press.
  260. It saves the diff shown in the dialog to a file in the local
  261. filesystem.
  262. """
  263. if type(self.filename) is types.ListType:
  264. if len(self.filename) > 1:
  265. fname = self.vcs.splitPathList(self.filename)[0]
  266. else:
  267. dname, fname = self.vcs.splitPath(self.filename[0])
  268. if fname != '.':
  269. fname = "%s.diff" % self.filename[0]
  270. else:
  271. fname = dname
  272. else:
  273. fname = self.vcs.splitPath(self.filename)[0]
  274. selectedFilter = QString("")
  275. fname = KQFileDialog.getSaveFileName(\
  276. self,
  277. self.trUtf8("Save Diff"),
  278. fname,
  279. self.trUtf8("Patch Files (*.diff)"),
  280. selectedFilter,
  281. QFileDialog.Options(QFileDialog.DontConfirmOverwrite))
  282. if fname.isEmpty():
  283. return
  284. ext = QFileInfo(fname).suffix()
  285. if ext.isEmpty():
  286. ex = selectedFilter.section('(*',1,1).section(')',0,0)
  287. if not ex.isEmpty():
  288. fname.append(ex)
  289. if QFileInfo(fname).exists():
  290. res = KQMessageBox.warning(self,
  291. self.trUtf8("Save Diff"),
  292. self.trUtf8("<p>The patch file <b>%1</b> already exists.</p>")
  293. .arg(fname),
  294. QMessageBox.StandardButtons(\
  295. QMessageBox.Abort | \
  296. QMessageBox.Save),
  297. QMessageBox.Abort)
  298. if res != QMessageBox.Save:
  299. return
  300. fname = unicode(Utilities.toNativeSeparators(fname))
  301. try:
  302. f = open(fname, "wb")
  303. f.write(unicode(self.contents.toPlainText()))
  304. f.close()
  305. except IOError, why:
  306. KQMessageBox.critical(self, self.trUtf8('Save Diff'),
  307. self.trUtf8('<p>The patch file <b>%1</b> could not be saved.'
  308. '<br>Reason: %2</p>')
  309. .arg(unicode(fname)).arg(unicode(why)))
  310. def __showError(self, msg):
  311. """
  312. Private slot to show an error message.
  313. @param msg error message to show (string or QString)
  314. """
  315. self.errorGroup.show()
  316. self.errors.insertPlainText(msg)
  317. self.errors.ensureCursorVisible()