Plugins/VcsPlugins/vcsSubversion/SvnLogDialog.py

branch
maintenance
changeset 5468
c307358a2ecc
parent 5447
852016bbdedb
parent 5467
44ab42f1e8b1
child 5469
b46f68bbd6b4
equal deleted inserted replaced
5447:852016bbdedb 5468:c307358a2ecc
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2003 - 2017 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to show the output of the svn log command process.
8 """
9
10 from __future__ import unicode_literals
11 try:
12 str = unicode
13 except NameError:
14 pass
15
16 import os
17
18 from PyQt5.QtCore import QTimer, QProcess, QRegExp, QUrl, pyqtSlot, qVersion, \
19 QByteArray
20 from PyQt5.QtGui import QTextCursor
21 from PyQt5.QtWidgets import QWidget, QLineEdit, QApplication, QDialogButtonBox
22
23 from E5Gui import E5MessageBox
24
25 from .Ui_SvnLogDialog import Ui_SvnLogDialog
26
27 import Utilities
28 import Preferences
29
30
31 class SvnLogDialog(QWidget, Ui_SvnLogDialog):
32 """
33 Class implementing a dialog to show the output of the svn log command
34 process.
35
36 The dialog is nonmodal. Clicking a link in the upper text pane shows
37 a diff of the versions.
38 """
39 def __init__(self, vcs, isFile=False, parent=None):
40 """
41 Constructor
42
43 @param vcs reference to the vcs object
44 @param isFile flag indicating log for a file is to be shown (boolean)
45 @param parent parent widget (QWidget)
46 """
47 super(SvnLogDialog, self).__init__(parent)
48 self.setupUi(self)
49
50 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
51
52 self.process = QProcess()
53 self.vcs = vcs
54
55 self.contents.setHtml(
56 self.tr('<b>Processing your request, please wait...</b>'))
57
58 self.process.finished.connect(self.__procFinished)
59 self.process.readyReadStandardOutput.connect(self.__readStdout)
60 self.process.readyReadStandardError.connect(self.__readStderr)
61
62 self.contents.anchorClicked.connect(self.__sourceChanged)
63
64 self.rx_sep = QRegExp('\\-+\\s*')
65 self.rx_sep2 = QRegExp('=+\\s*')
66 self.rx_rev = QRegExp(
67 'rev ([0-9]+): ([^|]*) \| ([^|]*) \| ([0-9]+) .*')
68 # "rev" followed by one or more decimals followed by a colon followed
69 # anything up to " | " (twice) followed by one or more decimals
70 # followed by anything
71 self.rx_rev2 = QRegExp(
72 'r([0-9]+) \| ([^|]*) \| ([^|]*) \| ([0-9]+) .*')
73 # "r" followed by one or more decimals followed by " | " followed
74 # anything up to " | " (twice) followed by one or more decimals
75 # followed by anything
76 self.rx_flags = QRegExp(' ([ADM])( .*)\\s*')
77 # three blanks followed by A or D or M
78 self.rx_changed = QRegExp('Changed .*\\s*')
79
80 self.flags = {
81 'A': self.tr('Added'),
82 'D': self.tr('Deleted'),
83 'M': self.tr('Modified')
84 }
85
86 self.revisions = [] # stack of remembered revisions
87 self.revString = self.tr('revision')
88
89 self.buf = [] # buffer for stdout
90 self.diff = None
91
92 self.sbsCheckBox.setEnabled(isFile)
93 self.sbsCheckBox.setVisible(isFile)
94
95 def closeEvent(self, e):
96 """
97 Protected slot implementing a close event handler.
98
99 @param e close event (QCloseEvent)
100 """
101 if self.process is not None and \
102 self.process.state() != QProcess.NotRunning:
103 self.process.terminate()
104 QTimer.singleShot(2000, self.process.kill)
105 self.process.waitForFinished(3000)
106
107 e.accept()
108
109 def start(self, fn, noEntries=0):
110 """
111 Public slot to start the cvs log command.
112
113 @param fn filename to show the log for (string)
114 @param noEntries number of entries to show (integer)
115 """
116 self.errorGroup.hide()
117 QApplication.processEvents()
118
119 self.intercept = False
120 self.filename = fn
121 self.dname, self.fname = self.vcs.splitPath(fn)
122
123 self.process.kill()
124
125 args = []
126 args.append('log')
127 self.vcs.addArguments(args, self.vcs.options['global'])
128 self.vcs.addArguments(args, self.vcs.options['log'])
129 if noEntries:
130 args.append('--limit')
131 args.append(str(noEntries))
132 self.activateWindow()
133 self.raise_()
134 args.append(self.fname)
135
136 self.process.setWorkingDirectory(self.dname)
137
138 self.process.start('svn', args)
139 procStarted = self.process.waitForStarted(5000)
140 if not procStarted:
141 self.inputGroup.setEnabled(False)
142 E5MessageBox.critical(
143 self,
144 self.tr('Process Generation Error'),
145 self.tr(
146 'The process {0} could not be started. '
147 'Ensure, that it is in the search path.'
148 ).format('svn'))
149
150 def __procFinished(self, exitCode, exitStatus):
151 """
152 Private slot connected to the finished signal.
153
154 @param exitCode exit code of the process (integer)
155 @param exitStatus exit status of the process (QProcess.ExitStatus)
156 """
157 self.inputGroup.setEnabled(False)
158 self.inputGroup.hide()
159
160 self.contents.clear()
161
162 lvers = 1
163 for s in self.buf:
164 rev_match = False
165 if self.rx_rev.exactMatch(s):
166 ver = self.rx_rev.cap(1)
167 author = self.rx_rev.cap(2)
168 date = self.rx_rev.cap(3)
169 # number of lines is ignored
170 rev_match = True
171 elif self.rx_rev2.exactMatch(s):
172 ver = self.rx_rev2.cap(1)
173 author = self.rx_rev2.cap(2)
174 date = self.rx_rev2.cap(3)
175 # number of lines is ignored
176 rev_match = True
177
178 if rev_match:
179 dstr = '<b>{0} {1}</b>'.format(self.revString, ver)
180 try:
181 lv = self.revisions[lvers]
182 lvers += 1
183 url = QUrl()
184 url.setScheme("file")
185 url.setPath(self.filename)
186 if qVersion() >= "5.0.0":
187 query = lv + '_' + ver
188 url.setQuery(query)
189 else:
190 query = QByteArray()
191 query.append(lv).append('_').append(ver)
192 url.setEncodedQuery(query)
193 dstr += ' [<a href="{0}" name="{1}">{2}</a>]'.format(
194 url.toString(), query,
195 self.tr('diff to {0}').format(lv),
196 )
197 except IndexError:
198 pass
199 dstr += '<br />\n'
200 self.contents.insertHtml(dstr)
201
202 dstr = self.tr('<i>author: {0}</i><br />\n').format(author)
203 self.contents.insertHtml(dstr)
204
205 dstr = self.tr('<i>date: {0}</i><br />\n').format(date)
206 self.contents.insertHtml(dstr)
207
208 elif self.rx_sep.exactMatch(s) or self.rx_sep2.exactMatch(s):
209 self.contents.insertHtml('<hr />\n')
210
211 elif self.rx_flags.exactMatch(s):
212 dstr = self.flags[self.rx_flags.cap(1)]
213 dstr += self.rx_flags.cap(2)
214 dstr += '<br />\n'
215 self.contents.insertHtml(dstr)
216
217 elif self.rx_changed.exactMatch(s):
218 dstr = '<br />{0}<br />\n'.format(s)
219 self.contents.insertHtml(dstr)
220
221 else:
222 if s == "":
223 s = self.contents.insertHtml('<br />\n')
224 else:
225 self.contents.insertHtml(Utilities.html_encode(s))
226 self.contents.insertHtml('<br />\n')
227
228 tc = self.contents.textCursor()
229 tc.movePosition(QTextCursor.Start)
230 self.contents.setTextCursor(tc)
231 self.contents.ensureCursorVisible()
232
233 def __readStdout(self):
234 """
235 Private slot to handle the readyReadStandardOutput signal.
236
237 It reads the output of the process and inserts it into a buffer.
238 """
239 self.process.setReadChannel(QProcess.StandardOutput)
240
241 while self.process.canReadLine():
242 line = str(self.process.readLine(),
243 Preferences.getSystem("IOEncoding"),
244 'replace')
245 self.buf.append(line)
246 if self.rx_rev.exactMatch(line):
247 ver = self.rx_rev.cap(1)
248 # save revision number for later use
249 self.revisions.append(ver)
250 elif self.rx_rev2.exactMatch(line):
251 ver = self.rx_rev2.cap(1)
252 # save revision number for later use
253 self.revisions.append(ver)
254
255 def __readStderr(self):
256 """
257 Private slot to handle the readyReadStandardError signal.
258
259 It reads the error output of the process and inserts it into the
260 error pane.
261 """
262 if self.process is not None:
263 self.errorGroup.show()
264 s = str(self.process.readAllStandardError(),
265 Preferences.getSystem("IOEncoding"),
266 'replace')
267 self.errors.insertPlainText(s)
268 self.errors.ensureCursorVisible()
269
270 def __sourceChanged(self, url):
271 """
272 Private slot to handle the sourceChanged signal of the contents pane.
273
274 @param url the url that was clicked (QUrl)
275 """
276 self.contents.setSource(QUrl(''))
277 filename = url.path()
278 if Utilities.isWindowsPlatform():
279 if filename.startswith("/"):
280 filename = filename[1:]
281 if qVersion() >= "5.0.0":
282 ver = url.query()
283 else:
284 ver = bytes(url.encodedQuery()).decode()
285 v1 = ver.split('_')[0]
286 v2 = ver.split('_')[1]
287 if v1 == "" or v2 == "":
288 return
289 self.contents.scrollToAnchor(ver)
290
291 if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked():
292 self.vcs.svnSbsDiff(filename, revisions=(v1, v2))
293 else:
294 if self.diff is None:
295 from .SvnDiffDialog import SvnDiffDialog
296 self.diff = SvnDiffDialog(self.vcs)
297 self.diff.show()
298 self.diff.start(filename, [v1, v2])
299
300 def on_passwordCheckBox_toggled(self, isOn):
301 """
302 Private slot to handle the password checkbox toggled.
303
304 @param isOn flag indicating the status of the check box (boolean)
305 """
306 if isOn:
307 self.input.setEchoMode(QLineEdit.Password)
308 else:
309 self.input.setEchoMode(QLineEdit.Normal)
310
311 @pyqtSlot()
312 def on_sendButton_clicked(self):
313 """
314 Private slot to send the input to the subversion process.
315 """
316 input = self.input.text()
317 input += os.linesep
318
319 if self.passwordCheckBox.isChecked():
320 self.errors.insertPlainText(os.linesep)
321 self.errors.ensureCursorVisible()
322 else:
323 self.errors.insertPlainText(input)
324 self.errors.ensureCursorVisible()
325
326 self.process.write(input)
327
328 self.passwordCheckBox.setChecked(False)
329 self.input.clear()
330
331 def on_input_returnPressed(self):
332 """
333 Private slot to handle the press of the return key in the input field.
334 """
335 self.intercept = True
336 self.on_sendButton_clicked()
337
338 def keyPressEvent(self, evt):
339 """
340 Protected slot to handle a key press event.
341
342 @param evt the key press event (QKeyEvent)
343 """
344 if self.intercept:
345 self.intercept = False
346 evt.accept()
347 return
348 super(SvnLogDialog, self).keyPressEvent(evt)

eric ide

mercurial