Plugins/VcsPlugins/vcsPySvn/SvnDiffDialog.py

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

eric ide

mercurial