Plugins/VcsPlugins/vcsPySvn/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
12 import os
13 import sys
14
15 import pysvn
16
17 from PyQt5.QtCore import QMutexLocker, QUrl, Qt, QByteArray, qVersion
18 from PyQt5.QtGui import QCursor, QTextCursor
19 from PyQt5.QtWidgets import QWidget, QApplication, QDialogButtonBox
20
21 from .SvnUtilities import formatTime
22
23 from .SvnDialogMixin import SvnDialogMixin
24 from .Ui_SvnLogDialog import Ui_SvnLogDialog
25
26 import Utilities
27
28
29 class SvnLogDialog(QWidget, SvnDialogMixin, Ui_SvnLogDialog):
30 """
31 Class implementing a dialog to show the output of the svn log command.
32
33 The dialog is nonmodal. Clicking a link in the upper text pane shows
34 a diff of the versions.
35 """
36 def __init__(self, vcs, isFile=False, parent=None):
37 """
38 Constructor
39
40 @param vcs reference to the vcs object
41 @param isFile flag indicating log for a file is to be shown (boolean)
42 @param parent parent widget (QWidget)
43 """
44 super(SvnLogDialog, self).__init__(parent)
45 self.setupUi(self)
46 SvnDialogMixin.__init__(self)
47
48 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
49 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
50
51 self.vcs = vcs
52
53 self.contents.setHtml(
54 self.tr('<b>Processing your request, please wait...</b>'))
55
56 self.contents.anchorClicked.connect(self.__sourceChanged)
57
58 self.flags = {
59 'A': self.tr('Added'),
60 'D': self.tr('Deleted'),
61 'M': self.tr('Modified')
62 }
63
64 self.revString = self.tr('revision')
65 self.diff = None
66
67 self.sbsCheckBox.setEnabled(isFile)
68 self.sbsCheckBox.setVisible(isFile)
69
70 self.client = self.vcs.getClient()
71 self.client.callback_cancel = \
72 self._clientCancelCallback
73 self.client.callback_get_login = \
74 self._clientLoginCallback
75 self.client.callback_ssl_server_trust_prompt = \
76 self._clientSslServerTrustPromptCallback
77
78 def start(self, fn, noEntries=0):
79 """
80 Public slot to start the svn log command.
81
82 @param fn filename to show the log for (string)
83 @param noEntries number of entries to show (integer)
84 """
85 self.errorGroup.hide()
86
87 fetchLimit = 10
88
89 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
90 QApplication.processEvents()
91
92 self.filename = fn
93 dname, fname = self.vcs.splitPath(fn)
94
95 opts = self.vcs.options['global'] + self.vcs.options['log']
96 verbose = "--verbose" in opts
97
98 self.activateWindow()
99 self.raise_()
100
101 locker = QMutexLocker(self.vcs.vcsExecutionMutex)
102 cwd = os.getcwd()
103 os.chdir(dname)
104 try:
105 nextRev = 0
106 fetched = 0
107 logs = []
108 limit = noEntries or 9999999
109 while fetched < limit:
110 flimit = min(fetchLimit, limit - fetched)
111 if fetched == 0:
112 revstart = pysvn.Revision(pysvn.opt_revision_kind.head)
113 else:
114 revstart = pysvn.Revision(
115 pysvn.opt_revision_kind.number, nextRev)
116 allLogs = self.client.log(fname,
117 revision_start=revstart,
118 discover_changed_paths=verbose,
119 limit=flimit + 1,
120 strict_node_history=False)
121 if len(allLogs) <= flimit or self._clientCancelCallback():
122 logs.extend(allLogs)
123 break
124 else:
125 logs.extend(allLogs[:-1])
126 nextRev = allLogs[-1]["revision"].number
127 fetched += fetchLimit
128 locker.unlock()
129
130 self.contents.clear()
131 self.__pegRev = None
132 for log in logs:
133 ver = "{0:d}".format(log["revision"].number)
134 dstr = '<b>{0} {1}</b>'.format(self.revString, ver)
135 if self.__pegRev is None:
136 self.__pegRev = int(ver)
137 try:
138 lv = "{0:d}".format(
139 logs[logs.index(log) + 1]["revision"].number)
140 url = QUrl()
141 url.setScheme("file")
142 url.setPath(self.filename)
143 if qVersion() >= "5.0.0":
144 query = lv + '_' + ver
145 url.setQuery(query)
146 else:
147 query = QByteArray()
148 query.append(lv).append('_').append(ver)
149 url.setEncodedQuery(query)
150 dstr += ' [<a href="{0}" name="{1}">{2}</a>]'.format(
151 url.toString(), query,
152 self.tr('diff to {0}').format(lv)
153 )
154 except IndexError:
155 pass
156 dstr += '<br />\n'
157 self.contents.insertHtml(dstr)
158
159 author = log["author"]
160 message = log["message"]
161 if sys.version_info[0] == 2:
162 author = author.decode('utf-8')
163 message = message.decode('utf-8')
164 dstr = self.tr('<i>author: {0}</i><br />\n')\
165 .format(author)
166 self.contents.insertHtml(dstr)
167
168 dstr = self.tr('<i>date: {0}</i><br />\n')\
169 .format(formatTime(log["date"]))
170 self.contents.insertHtml(dstr)
171
172 self.contents.insertHtml('<br />\n')
173
174 for line in message.splitlines():
175 self.contents.insertHtml(Utilities.html_encode(line))
176 self.contents.insertHtml('<br />\n')
177
178 if len(log['changed_paths']) > 0:
179 self.contents.insertHtml('<br />\n')
180 for changeInfo in log['changed_paths']:
181 action = changeInfo["action"]
182 path = changeInfo["path"]
183 if sys.version_info[0] == 2:
184 action = action.decode('utf-8')
185 path = path.decode('utf-8')
186 dstr = '{0} {1}'.format(self.flags[action], path)
187 if changeInfo["copyfrom_path"] is not None:
188 copyfrom_path = changeInfo["copyfrom_path"]
189 if sys.version_info[0] == 2:
190 copyfrom_path = copyfrom_path.decode('utf-8')
191 dstr += self.tr(
192 " (copied from {0}, revision {1})")\
193 .format(copyfrom_path,
194 changeInfo["copyfrom_revision"].number)
195 dstr += '<br />\n'
196 self.contents.insertHtml(dstr)
197
198 self.contents.insertHtml('<hr /><br />\n')
199 except pysvn.ClientError as e:
200 locker.unlock()
201 self.__showError(e.args[0])
202 os.chdir(cwd)
203 self.__finish()
204
205 def __finish(self):
206 """
207 Private slot called when the user pressed the button.
208 """
209 QApplication.restoreOverrideCursor()
210
211 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
212 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
213 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
214
215 tc = self.contents.textCursor()
216 tc.movePosition(QTextCursor.Start)
217 self.contents.setTextCursor(tc)
218 self.contents.ensureCursorVisible()
219
220 self._cancel()
221
222 def on_buttonBox_clicked(self, button):
223 """
224 Private slot called by a button of the button box clicked.
225
226 @param button button that was clicked (QAbstractButton)
227 """
228 if button == self.buttonBox.button(QDialogButtonBox.Close):
229 self.close()
230 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
231 self.__finish()
232
233 def __sourceChanged(self, url):
234 """
235 Private slot to handle the sourceChanged signal of the contents pane.
236
237 @param url the url that was clicked (QUrl)
238 """
239 self.contents.setSource(QUrl(''))
240 filename = url.path()
241 if Utilities.isWindowsPlatform():
242 if filename.startswith("/"):
243 filename = filename[1:]
244 if qVersion() >= "5.0.0":
245 ver = url.query()
246 else:
247 ver = bytes(url.encodedQuery()).decode()
248 v1 = ver.split('_')[0]
249 v2 = ver.split('_')[1]
250 if not v1 or not v2:
251 return
252 try:
253 v1 = int(v1)
254 v2 = int(v2)
255 except ValueError:
256 return
257 self.contents.scrollToAnchor(ver)
258
259 if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked():
260 self.vcs.svnSbsDiff(filename, revisions=(v1, v2))
261 else:
262 if self.diff is None:
263 from .SvnDiffDialog import SvnDiffDialog
264 self.diff = SvnDiffDialog(self.vcs)
265 self.diff.show()
266 self.diff.start(filename, [v1, v2], pegRev=self.__pegRev)
267
268 def __showError(self, msg):
269 """
270 Private slot to show an error message.
271
272 @param msg error message to show (string)
273 """
274 self.errorGroup.show()
275 self.errors.insertPlainText(msg)
276 self.errors.ensureCursorVisible()

eric ide

mercurial